RSpec のテストダブルで呼び出しているクラスやメソッドの変更を検出する方法についてまとめます。
スライド
まずは Sandi Metz 氏の自動テストに関するスライドをご覧ください。
Verifying doubles
スライドではテストダブルが実際にAPIと同期しているかチェックするためのライブラリ群が紹介されています。
RSpec3 からは該当機能が標準で取り込まれています。
例えば、 RSpec 2 で double を利用していた箇所を
RSpec 3 で instance_double, class_double 等を利用するように変更すると、
テストダブルの変更を検出してくれるようになります。
これにより該当クラスやメソッドの名前が変更された場合に変更を検出できるようになります。
さらに RSpec の設定値を変えることで検証範囲を変更することができます。
以下でサンプルを紹介します。
サンプル
ケース | verify_doubled_constant_names | エラーを検出できるか? |
---|---|---|
double でクラスが存在しないケース | – | ☓ |
double でメソッドが存在しないケース | – | ☓ |
instance_double でクラスが存在しないケース | false | ☓ |
instance_double でメソッドが存在しないケース | false | ○ |
instance_double でクラスが存在しないケース | true | ○ |
instance_double でメソッドが存在しないケース | true | ○ |
※今回はサンプルとして instance_double を使っていますが、
class_double, object_double などもあります。
double でクラスが存在しないケース
require "spec_helper" # class Hoge # def hoge # "hoge" # end # end class HogeUser def initialize(hoge) @hogea = hoge end def use @hogea.hoge end end describe HogeUser do it do hoge = double("Hoge") expect(hoge).to receive(:hoge) user = HogeUser.new(hoge) user.use end end
- 結果
正常終了してしまいます。
モック対象の変更やタイポをテストで検出することができません。
$ rspec spec/double_not_exist_class_spec.rb . Finished in 0.00512 seconds (files took 0.10376 seconds to load)
double でメソッドが存在しないケース
require "spec_helper" class Hoge # def hoge # "hoge" # end end class HogeUser def initialize(hoge) @hogea = hoge end def use @hogea.hoge end end describe HogeUser do it do hoge = double("Hoge") expect(hoge).to receive(:hoge) user = HogeUser.new(hoge) user.use end end
- 結果
正常終了してしまいます
モック対象の変更やタイポをテストで検出することができません。
$ rspec spec/double_not_exist_method_spec.rb . Finished in 0.00512 seconds (files took 0.10597 seconds to load)
mocks.verify_doubled_constant_names = false の場合
- spec_helper.rb
# 略 config.mock_with :rspec do |mocks| # ※設定を省略した場合はデフォルトで false になります mocks.verify_doubled_constant_names = false end # 略
instance_double でクラスが存在しないケース
require "spec_helper" # class Hoge # def hoge # "hoge" # end # end class HogeUser def initialize(hoge) @hogea = hoge end def use @hogea.hoge end end describe HogeUser do it do hoge = instance_double("Hoge") expect(hoge).to receive(:hoge) user = HogeUser.new(hoge) user.use end end
- 結果
正常終了してしまいます。
モック対象の変更やタイポをテストで検出することができません。
$ rspec spec/instance_double_not_exist_class_spec.rb . Finished in 0.00689 seconds (files took 0.10842 seconds to load) 1 example, 0 failures
instance_double でメソッドが存在しないケース
require "spec_helper" class Hoge def hoge "hoge" end end class HogeUser def initialize(hoge) @hogea = hoge end def use @hogea.hoge end end describe HogeUser do it do hoge = double("Hoge") # わざと誤ったメソッド名を指定 expect(hoge).to receive(:hogea) user = HogeUser.new(hoge) user.use end end
- 結果
メソッドが存在しないとエラーになります。
メソッド名の変更をエラーとして検出できます。
$ rspec spec/instance_double_not_exist_method_spec.rb F Failures: 1) HogeUser should receive hogea(*(any args)) 1 time Failure/Error: @hogea.hoge #<Double "Hoge"> received unexpected message :hoge with (no args) # ./spec/instance_double_not_exist_method_spec.rb:15:in `use' # ./spec/instance_double_not_exist_method_spec.rb:25:in `block (2 levels) in <top (required)>' Finished in 0.0051 seconds (files took 0.10938 seconds to load) 1 example, 1 failure Failed examples: rspec ./spec/instance_double_not_exist_method_spec.rb:20 # HogeUser should receive hogea(*(any args)) 1 time
mocks.verify_doubled_constant_names = true の場合
- spec_helper.rb
# 略 config.mock_with :rspec do |mocks| mocks.verify_doubled_constant_names = true end # 略
instance_double でクラスが存在しないケース
ソースコードは前の例と同じなので省略
- 結果
クラスが存在しないとエラーになります。
クラス名の変更をエラーとして検出できます。
$ rspec spec/instance_double_not_exist_class_spec.rb F Failures: 1) HogeUser Failure/Error: hoge = instance_double("Hoge") "Hoge" is not a defined constant. Perhaps you misspelt it? Disable check with `verify_doubled_constant_names` configuration option. # ./spec/instance_double_not_exist_class_spec.rb:21:in `block (2 levels) in <top (required)>' Finished in 0.00048 seconds (files took 0.1098 seconds to load) 1 example, 1 failure Failed examples: rspec ./spec/instance_double_not_exist_class_spec.rb:20 # HogeUser
instance_double でメソッドが存在しないケース
- ソースコードは
mocks.verify_doubled_constant_names = false
の例と同じなので省略 - 実行結果は
mocks.verify_doubled_constant_names = false
の例と同じなので省略
Verifying doubles についてまとめ
デフォルトではメソッドのチェックはしてくれますが、クラスの存在チェックはしてくれません。
mocks.verify_doubled_constant_names = true
を設定すればクラスの存在チェックもしてくれます。
なぜこの挙動がデフォルトなのか、よくわかっていません。