見出し画像

【ポンコツ版】.mock_in_sequence を作ってみました

追記 (2022/6/4):重大な欠陥が発覚しました。引数のある場合、正常に機能しません。修正を検討しなければいけなくなりました。

追記 (2022/6/4):修正を施した【陳謝版】は、こちら。


前回の記事のベタな解決法をmoduleにまとめてみました。

module MockInSequence
  def mock_in_sequence(target_method, exp_sequence_args)
    act_sequence  = Array.new
    mocks         = Hash.new
    exp_sequence  = exp_sequence_args.map(&:first)
    exp_sequence_args.reverse! # エラーが起きた際、レベルの深さが表示されるので、それに合わせるため処理順序を反転

    stub_stream   = "self.#{target_method};"
    exp_sequence_args.each do |(name, retval, args)|
      mocks[name] = MiniTest::Mock.new
      if args.nil?
        mocks[name].expect(:call, retval) { act_sequence << name }
      else
        mocks[name].expect(:call, retval) { act_sequence << name; args }
      end
      stub_stream = "self.stub(#{name.inspect}, mocks[#{name.inspect}]) do; #{stub_stream} end;"
    end
    eval(stub_stream) # 本能的にこの処理は宜しくないと思います。

    # 実行結果を収めたハッシュをMockVerifyに食べさせてインスタンスにして返しています。
    MockVerify.new({ mocks: mocks, expected: exp_sequence, actual: act_sequence })
  end

  class MockVerify
    def initialize(results)
      @mocks    = results[:mocks]
      @expected = results[:expected]
      @actual   = results[:actual]
    end

    def verify(test_obj)
      @mocks.values.map(&:verify)
      test_obj.assert_equal @expected,
                            @actual,
                            'note: not expected the order of sequence.'
    end
  end
end

こんな感じで以下のように使います。
不細工ですが、シンプルにまとまっていることは確かではないでしょうか。

require 'minitest/autorun'
class MyClassTest < Minitest::Test
  def setup
    @my_class = MyClass.new
  end

  def test_show
    @my_class.extend MockInSequence # ここで、モジュールをオブジェクトにエクステンドします。

    exp_sequence_args = [
      [:show_header,  true],
      [:show_details, true],
      [:show_footer,  true]
    ]
    results = @my_class.mock_in_sequence(:show, exp_sequence_args)
    results.verify(self) # 魔法の呪文selfが必要です。
  end
end

class MySubClassTest < Minitest::Test
  def setup
    @my_sub_class = MySubClass.new
  end

  def test_show
    @my_sub_class.extend MockInSequence # ここで、モジュールをオブジェクトにエクステンドします。

    condition = false
    exp_sequence_args = [
      [:show_header,  true],
      [:show_footer,  true],
      [:show_details, true, [condition]]
    ]
    results = @my_sub_class.mock_in_sequence(:show, exp_sequence_args)
    results.verify(self) # 魔法の呪文selfが必要です。
  end
end

module内部のポンコツ加減も相当なものですが、MiniTest内での
results.verify(self)selfって、何?
自分で作っておいてなんですが、不気味過ぎです。。。
module内のassert_equalを動かすためだけにselfを召喚してしまっています。
美しくない。。。ひじょーに不細工。

誰か良きアドバイスを。。。

module内でも怪しいことをしております。
eval(stub_stream)
悪魔の呪文evalを唱えてしまっています。

もっとプログラミング・スキルがあれば、なんとかなるのでしょうけど。

誰か良きアドバイスを。。。いや、助けて!

とりあえず、「動けば正義!」と自分に言い聞かせて生き抜くことにします。まぁ、ボチボチ、ダラダラと今後も続けていく予定です。

この記事が気に入ったらサポートをしてみませんか?