RSpec の最大の魅力は、let だと思うようになった。let を使った見通しのよい、DRY なスペックの書き方の覚え書き。
具体例
describe User do
describe '#admin?' do
subject { user.admin? }
let(:user) { FactoryGirl.create :user, admin: admin }
context "when user is administrator" do
let(:admin) { true }
it "returns true" do
expect(subject).to be_true
end
end
context "when user is not administrator" do
let(:admin) { false }
it "returns false" do
expect(subject).to be_false
end
end
end
end
ポイント
- describe と context を明確に使い分ける
- let の遅延評価を利用して、context 直下は let 変数によるパラメータの操作だけにし、before などは一切書かない
- このルールを守るとテストの見通しがすごく良くなる
- context 直上の describe は以下のルールを用いている
- subject, let(/let!), before, it, context の順で書く(let と let! は自分の中でまだ決まってない)
- context によって状況を切り分ける必要がある場合、変化する対象を let でくくりだす(上のサンプルでは、user 生成時の admin パラメータを let としてくくりだし、context 内で変化させている)
- let でくくり出した場合、必要があればこの場所にデフォルト値を書いておく
- context による切り分けが必要ないもので、かつ変数にしたい(名前をつけておきたい)ものに関しては、let! を用いて正格評価としておくと、意味論的にすっきりする
例えば、スタブした値を場合によって切り分けたい場合は以下のようになる(rspec, factory_girl の他に rspec-spies を使っている)。
describe ClipController do
describe "POST 'create'" do
let!(:clip) { FactoryGirl.create :clip }
let!(:params) do
{ 'clip' => { 'code' => 'hogehoge' } }
end
let(:successed) { true }
before do
Clip.stub(:new) { clip }
clip.stub(:save) { successed }
post 'create', params
end
it "creates a new Clip" do
expect(Clip).to have_received(:new).with('code' => 'hogehoge')
expect(clip).to have_received(:save)
end
context 'when successed to save' do
# 一見冗長だが、書いた方が context を明確にできて良い気がしている
let(:successed) { true }
it "redirects to 'show'" do
expect(response).to redirect_to clip_path(clip)
end
it "sets a flash[:notice]" do
expect(flash[:notice]).to_not be_nil
end
end
context 'when failed to save' do
let(:successed) { false }
it "renders template 'new'" do
expect(page).to render_template('new')
end
end
end
end
結論
RSpec 最大の魅力は let という遅延評価される変数を用意した事であると思っている。これを最大限に生かしたスペックをかければ、DRY かつ見通しのよいスペックが書けると思う。