Cucumber+Mockで悩んでいる

CucumberでMockに対するmethod呼び出しのexpectation(should_receive)をどのように書くか悩んでいる.

動くけど好きじゃない例

CucumberのexamplesにMockを使う例がある.
https://github.com/cucumber/cucumber/tree/master/examples/rspec_doubles

この例ではMockの作成とmethod呼び出しのexpectationの設定をGiven節に書いている.Then節には何も書いていない.

mocking.feature

Feature: Mocking
  In order to test external stuff
  I want to mock

  Scenario: Mock a transmogrifier
    Given I have a cardboard box
    When I poke it all is good

calvin_steps.rb

class CardboardBox
  def initialize(transmogrifier)
    @transmogrifier = transmogrifier
  end
  
  def poke
    @transmogrifier.transmogrify
  end
end

Given /^I have a cardboard box$/ do
  transmogrifier = double('transmogrifier')
  transmogrifier.should_receive(:transmogrify)
  @box = CardboardBox.new(transmogrifier)
end

When /^I poke it all is good$/ do
  @box.poke
end

これでテスト自体は問題なく動くのだけど,いくつか不満点がある.

不満点
  • "Given I have a cardboard box"で実行されるコードが"I have a cardbox"以上のことをやっている
    • transmogrifierに関するGiven節を追加してコードを分割すればいいのかもしれないけど…
  • method呼び出しのexpectationがGiven節に,methodの戻り値のassertionがThen節に分散してしまう

こんなふうに書きたい

method呼び出しのexpectationもmethodの戻り値のassertionもThen節に書きたい.

mocking.features

Feature: Mocking
  In order to test external stuff
  I want to mock

  Scenario: Mock a transmogrifier
    Given I have a cardboard box
    When I poke it
    Then all is ok

calvin_steps.rb

class CardboardBox
  def initialize(transmogrifier)
    @transmogrifier = transmogrifier
  end
  
  def poke
    @transmogrifier.transmogrify
  end
end

Given /^I have a cardboard box$/ do
  @transmogrifier = double('transmogrifier')
  @box = CardboardBox.new(transmogrifier)
end

When /^I poke it all is good$/ do
  @box.poke
end

Then /^all is ok/ do
  @transmogrifier.should_receive(:transmogrify)
end

しかし,このテストは失敗する.Then節はWhen節よりもあとに実行されるため,When節で@box.pokeが呼ばれる時点で@transmogrifierにexpectationがセットされていないことが原因.

以上

どうすればこの手のテストがスマートに書けるのかまだよく分かってないけど,Then節がWhen節よりも後に実行される以上,Given節にexpectationを書く方法しかないんだろうなーと思ってる.

もっといい書き方がありましたらぜひ教えて下さい.