minitest で stub, mock を使う
仕事のプロジェクトでminitestを使っている。
rspecと比較してstubの使い勝手がちょっと違うので適当にまとめておく。
- 目次
- ::stub
- 特定のオブジェクトのメソッドをstubする
- 特定のオブジェクトのメソッドをstubでmockオブジェクトを返す
- ::stub_any_instance
- 特定のクラスのインスタンスメソッドをstubする
- 特定のクラスのインスタンスメソッドをstubでmockオブジェクトを返す
- ::stub
前提として、minitestのstubはブロック内のみでstubが有効になる。rspecではit内すべてで有効になる。
本記事では下記Hogeクラスというコードをstubしていく。
class Hoge
def run
internal
end
def internal
'hello'
end
end
特定のオブジェクトのメソッドをstubする
まず特定のオブジェクトに対してstubするには、
hoge = Hoge.new
hoge.stub :internal, 'hello from mock' do
hoge.run # => 'hello from mock'
end
hoge.run # => 'hello'
というように書ける。
特定のオブジェクトのメソッドをstubでmockオブジェクトを返す
次に前述のstubにmockを返して、メソッドが呼ばれたか確認する。
hoge = Hoge.new
mock = Minitest::Mock.new.expect(:call, true, [])
hoge.stub :internal, mock do
hoge.run # => 'hello from mock'
end
mock.verify # => true
というように書ける。
Minitest::Mock#verify
はexpect で設定したメソッドが呼ばれない状態で実行すると例外になる。
特定のクラスのインスタンスメソッドをstubする
次は、特定のクラスすべてのインスタンスメソッドをstubする。
rspec3系だと下記のように書くコードをminitestで書く。
allow_any_instance_of(Object).to receive_messages(:foo => 'foo', :bar => 'bar')
o = Object.new
expect(o.foo).to eq('foo')
expect(o.bar).to eq('bar')
rpsecではallow_any_instance_of
の挙動をするメソッドはデフォルトで使えるけどminitestではminitest-stub_any_instance
というgemで使えるようになる。
Hoge.stub_any_instance :internal, 'hello from mock' do
Hoge.new.run # => 'hello from mock'
end
Hoge.new.run # => 'hello'
というように書ける。
特定のクラスのインスタンスメソッドをstubでmockオブジェクトを返す
次はstub_any_instance
でmockオブジェクトを返す。
mock = Minitest::Mock.new.expect(:call, true, [])
Hoge.stub_any_instance :internal, mock do
Hoge.new.run # => 'hello from mock'
end
mock.verify
で動くと思ってしまいがちだけど、実は下記のようなエラーになる。
TypeError: wrong argument type Minitest::Mock (expected Proc)
from /usr/local/lib/ruby/gems/2.4.0/gems/minitest-stub_any_instance-1.0.1/lib/minitest/stub_any_instance.rb:10:in `block (2 levels) in stub_any_instance'
この挙動は、mockオブジェクトを受け付けないことを示していて、下記プルリクによる影響ということがわかった。
https://github.com/codeodor/minitest-stub_any_instance/pull/2/files
要は今の仕様では.stub_any_instance
にmockオブジェクトは渡すことはできないため、このgemにプルリクを送るかアプリケーションの設計を見直すか下記のようなモンキーパッチを書くかなどをしなければならない
class_eval do
alias_method new_name, name
define_method(name) do |*args|
if val_or_callable.respond_to? :call then
if val_or_callable.class == Proc
instance_exec(*args, &val_or_callable)
else
val_or_callable.call(*args)
end
else
val_or_callable
end
end
end
まとめ
- minitestのstubはブロックの中だけ有効
.stub_any_instance
の引数にmockを直接渡せない
完。
-
category:
- ruby