LoginSignup

This article is a Private article. Only a writer and users who know the URL can access it.
Please change open range to public in publish setting if you want to share this article with other users.

More than 5 years have passed since last update.

RSpec Best Practice Controller編

Posted at

目的

RSpecは柔軟な記述が可能なので、書き手によって三者三様の書き方になってしまう。しかしながらコードはなるべく平準化したい。

1. Seperate your example with describe '#action_name'

Example

spec/controllers/blogs_controller_spec.rb
describe BlogsController do
  describe '#index' do
  end

  describe '#create' do
  end
end

2. Seperate your example with context such as parameters, sessions.

spec/controllers/blogs_controller_spec.rb
...
  describe '#create' do
    context 'when user login' do
      context 'with valid params' do
      end

      context 'with invalid params' do
      end
    end

    context 'when user not login' do
    end
  end
...

3. Always declare your request as subject

subject = 暗黙の主語. エクスペクテーションが実行されたとき、明示的に主語が渡っていない場合に評価され、戻り値が主語になる。

spec/controllers/blogs_controller_spec.rb
...
  describe  '#index' do
    subject { get :index }
    it { should be_ok }
    it { expect(subject).to render_template :index }
  end
end

expect(subject)という見た目が英語的に気になる場合は、暗黙の主語に代わりの名前をつけられる。

subject(:getting_list) { get :index }
it { expect(getting_list).to render_template :index }

4. Override parameters from inner context

外側のsubjectで使用する変数を内側のcontextから宣言する。

spec/controllers/books_controller_spec.rb

describe '#create' do
  subject(:creating_book) { post :create, book: book_attributes }
  let(:valid_book_attributes) do
    {
      title: '眼球譚',
      author: 'バタイユ',
      description: 'おもしろい'
    }
  end

  context 'with valid attributes' do
    let(:book_attributes) { valid_book_attributes }
    it { should redirect_to books_path }
    it { expect { creating_book }.to change(Book, :count).by(1) }
  end

  context 'with invalid attributes' do
    let(:book_attributes) { valid_book_attributes.merge(title: nil) }
    it { should render_template :new }
    it { expect { creating_book }.not_to change(Book, :count) }
  end
end

5. Always use expect(subject) instead of subject.should

Good

spec/controllers/blogs_controller.rb

describe '#create' do
  it 'should send mail to user' do
    creating_blog
    expect(ActionMailer::Base.deliveries.last.to).to match_array [user.email]
  end
end

Bad

spec/controllers/blogs_controller.rb

describe '#create' do
  it 'should send mail to user' do
    creating_blog
    ActionMailer::Base.deliveries.last.to).to.should == [user.email]
  end
end
...

レシーバのないshouldは使用して良い。
it { should be_ok }

6. Understand the diferrence between expect(subject) and expect { subject }

expect { subject }にはraise_error, change, throw_symbolなどが使える。下の例を見ると理解できるはず。

spec/controllers/blogs_controller.rb

describe '#create' do
  subject(:creating_blog) { post :create, blog: blog_attributes }
  
  it { expect { creating_blog }.to change(Blog, :count).by(1) }
  it { lambda { creating_blog }.should change(Blog, :count).by(1) } #=> 同じ

  it { expect { creating_blog }.not_to raise_error }

  # 下3つはすべて同じ   
  it { expect(creating_blog).to redirect_to blogs_path }
  it { creating_blog.should redirect_to blogs_path }

  it do
    creating_blog
    repsonse.should redirect_to blogs_path
  end
end
...

7. Don't use render_views too match

render_viewsするとtemplateを書き出してくれる。これは下記のようなテストに役立つ。

spec/controllers/blogs_controller_spec.rb

describe '#index' do
  subject { get :index }
  before { 3.times { create(:blog) } }

  it { should render_template :index }
  it { expect(subject.body).to have_css('.blog', count: 3) }
end  
...

render_viewsすることでresponse.bodyがセットされ、view側のNoMethodErrorがないかなどがカジュアルにテストできるが、処理が重くなる。したがって浅いdescribe/contextでrender_viewsを指定するのはよくない。

8. Don't stick to DRY

テストコードでDRYにこだわりすぎるとshared_examplesなどが複雑化してよくわからなくなってくる。巨大なshared_examples, shared_contextの出現に気をつける。動的にテストコードを作るなどもあまりよくない。

9. Don't assign shared_context/shared_examples variables from example code.

Good

spec/spec_helpers/shared_context/time.rb
shared_context 'current time is' do |time = Time.now|
  around(:each) do |example|
    Timecop.freeze(time)
    example.run
    Timecop.return
  end
end
spec/controllers/blogs_controller_spec.rb
include_context 'current time is', Time.local(2013, 10, 1, 15, 0)

Bad

spec/spec_helpers/shared_context/time.rb
shared_context 'fix current time'
  around(:each) do |example|
    Timecop.freeze(time)
    example.run
    Timecop.return
  end
end
spec/controllers/blogs_controller_spec.rb
let(:time) { Time.local(2013, 10, 1, 15, 0) }
include_context 'fix current time'

10. Don't have responsibility to build test data.

Good

spec/controllers/blogs_controller_spec.rb
before { create(:blog_with_comments, comment_count: 3) }

Bad

spec/controllers/blogs_controller_spec.rb
before do
  blog = create(:blog)
  3.times { create(:comment, blog: blog) }
end

FactoryGirlなど、テストデータ作成側で吸収すべき。

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