LoginSignup
9
11

More than 5 years have passed since last update.

Rspecを書くときに参考にしたQiita記事

Last updated at Posted at 2018-12-25

セッティングから基礎知識まで

詳細

everyday Rspec

user_spec.rb
describe User do
  # 姓、名、メール、パスワードがあれば有効な状態であること
  it "is valid with a first name, last name, email, and password"
  # 名がなければ無効な状態であること
  it "is invalid without a first name"
  # 姓がなければ無効な状態であること
  it "is invalid without a last name"
  # メールアドレスがなければ無効な状態であること
  it "is invalid without an email address"
  # 重複したメールアドレスなら無効な状態であること
  it "is invalid with a duplicate email address"
  # ユーザーのフルネームを文字列として返すこと
  it "returns a user's full name as a string"
end

説明

①期待する結果をまとめて記述(describe)している。
このケースではUserモデルがどんなモデルなのか、そしてどんな振る舞いをするのかということを説明している。

②example(itで始まる1行)一つにつき、結果を一つだけ期待している。
first_name, last_name, emailのバリデーションをそれぞれ分けてテストしている点に注意する。
こうすれば、example が失敗したときに問題が起きたバリデーションを特定できる。
原因調査のためにRSpecの出力結果を調べる必要はありません。
少なくともそこまで細かく調べずに済む。

③どのexampleも明示的である。技術的なことを言うと、itのあとに続く説明用の文字列は必須ではない。
しかし、省略してしまうとスペックが読みにくくなるため記述する。

④各exampleの説明は動詞で始まっている。shouldではない。
期待する結果を声に出して読んでみると、下記の通り。
User is invalid without a first name (名がなければユーザーは無効な状態である)
User is invalid without a last name (姓がなければユーザーは無効な状態である)
User returns a user’s full name as a string (ユーザーは文字列としてユーザーのフルネームを返す)
可読性は 非常に重要であり、RSpec のキーとなる機

モデルのexpectとis_expectedの違い

belongs_toをチェックしたい

carrierwaveのテストをしたい

factories/samples_files.rb
FactoryBot.define do
  factory :sample_file do
    id: 1
    file Rack::Test::UploadedFile.new(File.join(Rails.root, 'spec/fixtures/sample.pdf'))
  end
end

しかし、↑のファイル指定はrubocopに怒られた、、
参考:https://github.com/rubocop-hq/rubocop/issues/3779

修正版
FactoryBot.define do
  factory :sample_file do
    id: 1
    file Rack::Test::UploadedFile.new(Rails.root.join('spec', 'fixtures', 'sample.pdf'))
  end
end

uniqunessのテストをしたい

この処理繰り返し使いたい、、

shared_contextを使うと良さげです。
https://magazine.rubyist.net/articles/0035/0035-RSpecInPractice.html
Rspecのshared_context基礎の基礎

spec/models/user_spec.rb

require 'rails_helper'

describe Person do
  let(:person) { Person.new(age) }

  shared_context 'infancy' do
    let(:age) { 19 }
  end

  shared_context 'adult' do
    let(:age) { 20 }
  end

  describe '#auth_smoke?' do
    context 'when age greater equals to 20' do
      include_context 'adult'
      subject { person.auth_smoke? }
      it { is_expected.to be_truthy }
    end
    context 'when age less than 20' do
      include_context 'infancy'
      subject { person.auth_smoke? }
      it { is_expected.to be_falsy }
    end
  end
end

let!とbeforeの違いって何?

結論:書いた順に評価される
let!が先であれば、beforeに先立つし、逆であれば、beforeが先に評価される。
明確な違いが無いのであろうか。。?

subjectとis_expectedをセットで使う

subject(:user) { User.new(email: "hoge@example.com", password: "password") }

https://qiita.com/mm36/items/453d71d48dd8f462344c
http://kei-p3.hatenablog.com/entry/2016/05/25/235124

特定のテストをスキップしたいとき

pendingを用いて、テストが失敗しないようにする。

ちなみに、xitとするだけで、特定のexampleをスキップ可能なので覚えておく。

Rspecでenumを使いたいとき

enumで数値型カラムを作る際に、テストデータを作る時に注意することを参考に

buildとcreateの違いを教えて

  • build()メソッドはインスタンスをメモリ上にのみ記録する。

  • create()メソッドはテストデータベースにも保存して、データを永続化させる。

FactoryBotで出来ることって何があるの?

記事はFactoryGirlに関する内容ですが、学べることが多いです。
https://tech.grooves.com/entry/2015/04/28/173025

あとは公式ドキュメントを読んでみる

インスタンスメソッドをテストしたい

例えば、userの姓と名を連結するメソッドnameがあるとする。

app/models/user.rb
def name
  [firstname, lastname].join(' ')
end

これをテストしてみる。

spec/models/user_spec.rb
it "returns a user's full name as a string" do
  user = User.new(
    first_name: "John",
    last_name:  "Doe",
    email:      "johndoe@example.com",
  )
  expect(user.name).to eq "John Doe"
end

※Rspecで等値のExpectationを書く時は、 ==ではなく、eqを使う。

スコープをテストしたい

Noteモデルに、渡された文字列でメモ(note)を検索する機能を付与。

app/models/note.rb
scope :search, ->(term) { 
  where("LOWER(message) LIKE ?", "%#{term.downcase}%")
}
spec/models/note_spec.rb
require 'rails_helper'

Rspec.describe Note, type: :model do
  before do
    @user = User.create(
      first_name: "John",
      last_name:  "Doe",
      email:      "johndoe@example.com",
      password:   "password",
    )

    @project = @user.projects.create(
      name: "Test Project",
    )
  end

  # ユーザー、プロジェクト、メッセージがあれば有効な状態であること
  it "is valid with a user, project, and message" do
    note = Note.new(
      message: "This is a sample note.",
      user: @user,
      project: @project,
    )
    expect(note).to be_valid
  end

  # メッセージがなければ無効な状態であること
  it "is invalid without a message" do
    note = Note.new(message: nil)
    note.valid?
    expect(note.errors[:message]).to include("can't be blank")
  end

  # 文字列に一致するメッセージを検索する
  describe "search message for a team" do
    before do
      @note1 = @project.notes.create(
        message: "this is the first note.",
        user: @user,
      )
      @note2 = @project.notes.create(
        message: "this is the second note.",
        user: @user,
      )
      @note3 = @project.notes.create(
        message: "First, preheat the oven",
        user: @user,
      )
    end

    # 一致するデータが見つかったとき
    context "when a match is found" do
      # 検索文字列に一致するメモを返すこと
      it "returns notes that match the search term" do
        expect(Note.search("first")).to include(@note1, @note3)
      end
    end

    # 一致するデータが1件も見つからないとき
    context "when no match is found" do
      # 空のコレクションを返すこと
      it "returns an empty collection" do
        expect(Note.search("message")).to be_empty
      end
    end
  end
end

FactoryBotで同時に複数個のインスタンスを生成したい

生成系のメソッドのそれぞれに対応する*_list()メソッドで、同じデータを複数件生成できる。
build_list(), build_stubbed_list(), create_list(), attributes_for_list()

users = build_list(:user, 5, name: )
9
11
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
9
11