RSpec Best Practice

Dec 14, 2012  

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 かつ見通しのよいスペックが書けると思う。