LoginSignup
172
112

More than 5 years have passed since last update.

RSpec で subject & expect する時の注意点

Posted at

どの段階でテストコードが評価されているか注意する。

expectis_expected 何が違うの??な人のための記事です。

subject を使うのはどういうとき?

定義元のコメントを見てみると・・

rspec/core/memoized_helpers.rb
# Declares a `subject` for an example group which can then be wrapped
# with `expect` using `is_expected` to make it the target of an
# expectation in a concise, one-line example.
# ...
def subject(name=nil, &block)
  if name
    let(name, &block)
    alias_method :subject, name

    self::NamedSubjectPreventSuper.__send__(:define_method, name) do
      raise NotImplementedError, "`super` in named subjects is not supported"
    end
  else
    let(:subject, &block)
  end
end

is_expected を使って簡潔な一行 example を書くため、らしい。
なのでsubject と is_expected はペアで使う。

subject の実態は要するに let (※letがわからない場合はここが詳しい解説)

そして let! ではないので、この時点ではまだ評価されてない。

is_expected は何なのか?

spec/controllers/books_controller_spec.rb
# Wraps the `subject` in `expect` to make it the target of an expectation.
# ...
# Designed to read nicely for one-liners.
def is_expected
  expect(subject)
end

インスタンス変数の subject を呼び出してる。
なのでこいつをペアで呼んであげると、subjectに渡したブロックが評価される。

特にハマリポイントはない王道パターンの使い方

spec/controllers/books_controller_spec.rb
RSpec.describe BooksController do

 describe "GET 'index'" do
  subject(:action) { get 'index' }
  it { is_expected.to be_success }  # OK
 end
end

expect を使いたい時は?

is_expected じゃなくて expect だと何が違う?

spec/controllers/books_controller_spec.rb

 # さっきのテストコードの続き

 let(:param) { {title: 'My Book'} } # 適当にcreateが成立するパラメータが定義されてるとする

 # ステータスコードチェックしてみる
 describe "POST 'create'" do
  # json のリクエストみたいに成功したら 201 (:craeted)が戻るコントローラーを例に
  subject(:action) { post 'create', params }
  it { expect(subject.status).to eq 201 }            # 1.OK
  it { expect(action.status).to eq 201 }             # 2.OK
  it { expect(response.status).to eq 201 }           # 3.NG
 end

それぞれ何をやってるか

1. expect(subject.status).to eq 201

subject -> let(:subject)で定義されたブロック(上の例で { post 'create' }のとこ)を評価して、 subject.status をチェック

2. expect(action.status).to eq 201

subject の name として action が渡ってるので、 ここでは subject == action
1と同じ。

3. expect(response.status).to eq 201

subject を評価していないので、 インスタンス変数 response の値は初期値のまま変わってない。
のでエラー
(status==200が初期値なので、GET#index の所で 200 チェックしてると、この現象に気づきづらい)

通したい場合

expect(subject) で評価

 describe "POST 'create'" do
  subject(:action) { post 'create', params }
  it do
    expect(subject) # ここで評価
    expect(response.status).to eq 201
  end
 end

subject だけでも良い

 describe "POST 'create'" do
  subject(:action) { post 'create', params }
  it do
    subject # ここで評価
    expect(response.status).to eq 201
  end
 end

subject!

let, let!の関係から推測・・

 describe "POST 'create'" do
  subject!(:action) { post 'create', params }
  it do
    expect(response.status).to eq 201
  end
 end

でもOK :)


statusチェックしたいだけなら is_expected でもできる

  it { is_expected.to have_http_status(201) }        # OK
  it { is_expected.to have_attributes(status: 201) } # OK

・・のでこんなとこでハマる人居ないはず;;

assigns チェックの時はこんな感じ

  let!(:books) { FactoryGirl.create_list(:book, 10) }
  subject(:action) { get 'index' }
  it do
    expect(subject)
    expect(assigns(:books)).to eq(books)
  end
  let!(:books) { FactoryGirl.create_list(:book, 10) }
  subject(:action) { get 'index' }
  it do
    subject
    expect(assigns(:books)).to eq(books)
  end
  let!(:books) { FactoryGirl.create_list(:book, 10) }
  subject!(:action) { get 'index' }
  it do
    expect(assigns(:books)).to eq(books)
  end

上の「通したい場合」のどのパターンでも良い。

172
112
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
172
112