インスタンス・オブジェクト内の異なるメソッドを順番にモックする方法を考えてみた。
Rubyで遊び始めて3年ほど経つでしょうか。それっぽい物も作れるようになってきたので、関心を持ってもらえそうなものを少しづつ投稿してみようと思います。
下のような2つのクラスにある.showメソッド内の異なるメソッドを実行順も併せてモックする必要が出てきたので、考えてみました。
class MyClass
def show
show_header
show_details
show_footer
end
def show_header; end
def show_details(condition = true); end
def show_footer; end
end
class MySubClass < MyClass
def show
show_header
show_footer
condition = false
show_details(condition) # このメソッドが最後に位置し、引数も伴って変更されています。
end
end
ものすごくベタに書くとMiniTestでは、こうなるようです。
require 'minitest/autorun'
class MyClassTest < Minitest::Test
def setup
@my_class = MyClass.new
end
def test_show
exp_sequence = [:show_header, :show_details, :show_footer]
pass_sequence = []
mock_show_header = MiniTest::Mock.new
mock_show_details = MiniTest::Mock.new
mock_show_footer = MiniTest::Mock.new
# {...}は、mockからは無視されている
mock_show_header.expect(:call, true) { pass_sequence << :show_header }
mock_show_details.expect(:call, true) { pass_sequence << :show_details }
mock_show_footer.expect(:call, true) { pass_sequence << :show_footer }
@my_class.stub :show_header, mock_show_header do
@my_class.stub :show_details, mock_show_details do
@my_class.stub :show_footer, mock_show_footer do
@my_class.show
end
end
end
mock_show_header.verify
mock_show_details.verify
mock_show_footer.verify
assert_equal exp_sequence,
pass_sequence,
'note: not expected the order of sequence'
end
end
class MySubClassTest < Minitest::Test
def setup
@my_sub_class = MySubClass.new
end
def test_show
condition = false
exp_sequence = [:show_header, :show_footer, :show_details]
pass_sequence = []
mock_show_header = MiniTest::Mock.new
mock_show_details = MiniTest::Mock.new
mock_show_footer = MiniTest::Mock.new
mock_show_header.expect(:call, true) { pass_sequence << :show_header }
mock_show_details.expect(:call, true) { pass_sequence << :show_details; [condition] }
# 最後に実行した[condition]が、mock_show_detailsの引数として評価される
mock_show_footer.expect(:call, true) { pass_sequence << :show_footer }
@my_sub_class.stub :show_header, mock_show_header do
@my_sub_class.stub :show_details, mock_show_details do
@my_sub_class.stub :show_footer, mock_show_footer do
@my_sub_class.show
end
end
end
mock_show_header.verify
mock_show_details.verify
mock_show_footer.verify
assert_equal exp_sequence,
pass_sequence,
'note: not expected the order of sequence'
end
end
.expectは、モックするメソッドが
引数を伴わない時、ブロック内の評価は無視引数を伴う時は、モックの引数とブロック内の最終評価を比較する
ようです。(2022/6/4 完全に間違えています。修正を検討しなければいけなくなりました。)
.expectのブロック内へ、pass_sequenceに通過したメソッドのシンボルを溜め込むことで、実行順を捕捉しています。他の部分は、通常の使い方のままですので、説明は不要だと思います。
ものすごくベタな解決方法なので、
メソッドにまとめた【ポンコツ版】.mock_in_sequenceを考えてみたのですが、あまりにポンコツで恥ずかしいですが、後日、投稿したいと思います。
なにぶん、本人もポンコツなので、アドバイスなどが貰えたら幸いです。
追記:なぜか続きの記事が表示されていないようなので、ここにリンクを入れておきます。