【陳謝版】.mock_in_sequenceを作ってみました。
適当な投稿をしてしまい申しわけありませんでした。
argsの付いた場合の評価がまったくされていなかったのですが、
なんとか修正が出来そうです。
まずは、修正結果から。
# Purpose: The methods in a target method are mocked in sequence
# mock_in_sequence(object, target_method, exp_sequence_args)
# object: testing object; instance object
# target_method: target method; symbol
# exp_sequence_args: exp_sequence_args is an array of methods
# and arguments that you want to mock in order
# exp_sequence_args = [
# [:method, retval, [args]],
# [:method, retval, [args]],
# ...
# ]
# eg. class MyClass
# def show
# show_header
# show_detail(arg1, arg2)
# show_footer
# end
# end
#
# class MyClassTest < MiniTest::Test
# include MockInSequence
#
# def setup
# @my_class = MyClass.new
# end
#
# def test_show
# exp_sequence_args = [
# [:show_header, true],
# [:show_detail, true, [arg1, arg2]],
# [:show_footer, true]
# ]
# results = mock_in_sequence(@my_class, :show, exp_sequence_args)
# results.verify
# end
# end
module MockInSequence
def mock_in_sequence(object, target_method, exp_sequence_args)
stub_stream = "object.#{target_method};"
act_sequence = Array.new
mocks = Hash.new
exp_sequence = exp_sequence_args.map(&:first)
exp_sequence_args.reverse!
exp_sequence_args.each do |(name, retval, exp_args)|
mocks[name] = MiniTest::Mock.new
if exp_args.nil?
mocks[name].expect(:call, retval) { act_sequence << name }
else
mocks[name].expect(:call, retval) { |*act_args| act_sequence << name; act_args == exp_args }
end
stub_stream = "object.stub(#{name.inspect}, mocks[#{name.inspect}]) do; #{stub_stream} end;"
end
eval(stub_stream)
MockVerify.new({ mocks: mocks, expected: exp_sequence, actual: act_sequence }, self)
end
class MockVerify
def initialize(results, test_obj)
@test_obj = test_obj
@mocks = results[:mocks]
@expected = results[:expected]
@actual = results[:actual]
end
def verify
@mocks.values.map(&:verify)
@test_obj.assert_equal @expected,
@actual,
'note: not expected the order of sequence.'
end
end
end
コード中、
if exp_args.nil?
mocks[name].expect(:call, retval) { act_sequence << name }
else
mocks[name].expect(:call, retval) { |*act_args| act_sequence << name;
act_args == exp_args }
end
と変更しています。
理由は、mock.rbの中を覗いてみると、
if val_block then
# keep "verify" happy
@actual_calls[sym] << expected_call
raise MockExpectationError, "mocked method %p failed block w/ %p" %
[sym, args] unless val_block.call(*args, &block)
return retval
end
のコメントに# keep "verify" happyとある箇所です。
ここが、expectのブロック指定の処理になるようです。
val_block.call(*args, &block)を実行して、falseを返したらMockExpectationErrorになり、trueを返すとretvalを返して抜けるようになっています。
よく見ると、val_block.callの引数に*argsがあり、この中には、act側の
argsが入っています。
このact側のargsとexp側のargsをブロック内で比較してtrueを返せれば、
正常に抜けることができることになります。
ただ、残念なのは、テストに失敗するとMockExpectationErrorとしてerrorカウントされます。本来は、failureとしてカウントして欲しいのですが。。。
迷惑の掛かった方がみえましたら、申しわけなかったです。
もうちょっと気を付けないといけないですよね。反省。。。
全体のコードは、以下のようになります。
# Purpose: The methods in a target method are mocked in sequence
# mock_in_sequence(object, target_method, exp_sequence_args)
# object: testing object; instance object
# target_method: target method; symbol
# exp_sequence_args: exp_sequence_args is an array of methods
# and arguments that you want to mock in order
# exp_sequence_args = [
# [:method, retval, [args]],
# [:method, retval, [args]],
# ...
# ]
# eg. class MyClass
# def show
# show_header
# show_detail(arg1, arg2)
# show_footer
# end
# end
#
# class MyClassTest < MiniTest::Test
# include MockInSequence
#
# def setup
# @my_class = MyClass.new
# end
#
# def test_show
# exp_sequence_args = [
# [:show_header, true],
# [:show_detail, true, [arg1, arg2]],
# [:show_footer, true]
# ]
# results = mock_in_sequence(@my_class, :show, exp_sequence_args)
# results.verify
# end
# end
module MockInSequence
def mock_in_sequence(object, target_method, exp_sequence_args)
stub_stream = "object.#{target_method};"
act_sequence = Array.new
mocks = Hash.new
exp_sequence = exp_sequence_args.map(&:first)
exp_sequence_args.reverse!
exp_sequence_args.each do |(name, retval, exp_args)|
mocks[name] = MiniTest::Mock.new
if exp_args.nil?
mocks[name].expect(:call, retval) { act_sequence << name }
else
mocks[name].expect(:call, retval) { |*act_args| act_sequence << name; act_args == exp_args }
end
stub_stream = "object.stub(#{name.inspect}, mocks[#{name.inspect}]) do; #{stub_stream} end;"
end
eval(stub_stream)
MockVerify.new({ mocks: mocks, expected: exp_sequence, actual: act_sequence }, self)
end
class MockVerify
def initialize(results, test_obj)
@test_obj = test_obj
@mocks = results[:mocks]
@expected = results[:expected]
@actual = results[:actual]
end
def verify
@mocks.values.map(&:verify)
@test_obj.assert_equal @expected,
@actual,
'note: not expected the order of sequence.'
end
end
end
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
require 'minitest/autorun'
class MyClassTest < Minitest::Test
include MockInSequence
def setup
@my_class = MyClass.new
end
def test_show
exp_sequence_args = [
[:show_header, true],
[:show_details, true],
[:show_footer, true]
]
results = mock_in_sequence(@my_class, :show, exp_sequence_args)
results.verify
end
end
class MySubClassTest < Minitest::Test
include MockInSequence
def setup
@my_sub_class = MySubClass.new
end
def test_show
condition = false
exp_sequence_args = [
[:show_header, true],
[:show_footer, true],
[:show_details, true, [condition]]
]
results = mock_in_sequence(@my_sub_class, :show, exp_sequence_args)
results.verify
end
end
この記事が気に入ったらサポートをしてみませんか?