はじめに
スタブ(stub)とは、テスト中で呼ばれるメソッド処理の代用品のようなものです。
テストで呼ばれるメソッドの処理を指定した処理で置き換えることができます。
「外部サービスの呼び出し処理などを置き換えることによって仮状態で動かして、本当にテストしたい部分へ簡易的に辿りつきたい」といったときによくスタブを使います。
以下では例として次のようなSampleService
のテストを作成することを考えます。
class SampleService
def initialize; end
def call
is_error1 = call_api1
is_error2 = CallApi2Service.new.call
return 'error!' if is_error1 || is_error2
'success!'
end
private
def call_api1
# 外部API1の呼び出し処理
end
end
class CallApi2Service
def initialize; end
def call
# 外部API2の呼び出し処理
end
end
「2つの外部APIを両方ともエラーなく呼び出せたときに’success!’
が返却される」ということのテストを次のように作成したとします。
class SampleServiceTest < MiniTest::Test
def test_success
sample_service = SampleService.new
assert_equal('success!', sample_service.call)
end
end
このとき外部API1と外部API2をテスト実行の度に呼び出すことになります。
また外部API1や外部API2との通信が必ずしも成功するとは限りません。
そこで2つの外部API呼び出し処理を別の処理で置き換えて、通信エラーが生じない状況をテスト実行の度に再現できるようにしていきます。
stub
stub
を使うと、特定のオブジェクトのメソッドを他の処理で置き換えることができます。
SampleService
のcall_api1
メソッドを、stub
によって「(外部API1を呼び出すことなく)falseを返却する」という処理で置き換えると次のようになります。
class SampleServiceTest < MiniTest::Test
def test_success
sample_service = SampleService.new
sample_service.stub(:call_api1, false) do
assert_equal('success!', sample_service.call)
end
end
end
これで「外部API1をエラーなく呼び出す」という状態をテストの度に再現できるようになりました。
では外部API2の呼び出し処理も同様に置き換えようとするとどうなるでしょうか。
stub
は特定のオブジェクトに対して用いるメソッドです。
そのためテストで外部API2の呼び出し処理をstub
によって置き換えるには、テストを実行したときに生成されるCallApi2Service
インスタンスに対してstub
を適用する必要がありますが、これは非常に困難です。
また仮に次のようにCallApi2Service
クラスに対してstub
を使った場合は、CallApi2Service
クラスのクラスメソッドcall
を置き換える扱いとなり、「Minitest::UnexpectedError: NameError: undefined method 'call' for class 'CallApi2Service'
」のようなエラーを生じます。
class SampleServiceTest < MiniTest::Test
def test_success
sample_service = SampleService.new
sample_service.stub(:call_api1, false) do
CallApi2Service.stub(:call, false) do
assert_equal('success!', sample_service.call)
end
end
end
end
こんなときに使えるのがstub_any_instance
です。
stub_any_instance
を用いて(「特定のオブジェクトに対して」ではなく)「特定のクラスから生成される全てのインスタンスに対して」インスタンスメソッドの処理を置き換えます。
stub_any_instance
stub_any_instance
を使うと、特定のクラスのインスタンスメソッドを他の処理で置き換えることができます。
特定のクラスのインスタンス全てに対してメソッド処理の置き換えが行われるため、CallApi2Service
インスタンスのようにテストしたいメソッドの中で生成されるものに対して処理の置き換えをしたい場合に便利です。
stub_any_instance
を使うには次のgemを追加する必要があります。
gem 'minitest-stub_any_instance'
外部API2の呼び出し処理(CallApi2Service
のcall
メソッド)を、stub_any_instance
によって「(外部API2を呼び出すことなく)falseを返却する」という処理で置き換えると次のようになります。
class SampleServiceTest < MiniTest::Test
def test_success
sample_service = SampleService.new
sample_service.stub(:call_api1, false) do
CallApi2Service.stub_any_instance(:call, false) do
assert_equal('success!', sample_service.call)
end
end
end
end
これで「2つの外部APIを両方ともエラーなく呼び出せたときに’success!’
が返却される」ということをテストできるようになりました。
まとめ
- スタブを使うとテストで実行される処理を別の処理に置き換えることができる
- 特定のオブジェクトのメソッドを他の処理で置き換えたいとき →
stub
- 特定のクラスのインスタンス全てに対してインスタンスメソッドを他の処理で置き換えたいとき →
stub_any_instance