目的
RSpecは柔軟な記述が可能なので、書き手によって三者三様の書き方になってしまう。しかしながらコードはなるべく平準化したい。
1. Seperate your example with describe '#action_name'
Example
describe BlogsController do
describe '#index' do
end
describe '#create' do
end
end
2. Seperate your example with context such as parameters, sessions.
...
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
= 暗黙の主語. エクスペクテーションが実行されたとき、明示的に主語が渡っていない場合に評価され、戻り値が主語になる。
...
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から宣言する。
…
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
…
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
…
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
などが使える。下の例を見ると理解できるはず。
…
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を書き出してくれる。これは下記のようなテストに役立つ。
…
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
shared_context 'current time is' do |time = Time.now|
around(:each) do |example|
Timecop.freeze(time)
example.run
Timecop.return
end
end
include_context 'current time is', Time.local(2013, 10, 1, 15, 0)
Bad
shared_context 'fix current time'
around(:each) do |example|
Timecop.freeze(time)
example.run
Timecop.return
end
end
let(:time) { Time.local(2013, 10, 1, 15, 0) }
include_context 'fix current time'
10. Don't have responsibility to build test data.
Good
before { create(:blog_with_comments, comment_count: 3) }
Bad
before do
blog = create(:blog)
3.times { create(:comment, blog: blog) }
end
FactoryGirlなど、テストデータ作成側で吸収すべき。