prepend されたモジュールの private メソッドに対する any_instance スタブの制限
RSpec Mocks の any_instance スタブが、prepend されたモジュールで定義された private/protected メソッドを誤って許可していた問題が修正されました。この修正により、prepend を使用したコードでのテスト間の状態漏洩が防止されます。
背景
RSpec Mocks は、prepend されたモジュールで定義されたメソッドに対する any_instance スタブを禁止する設計になっています。prepend されたメソッドをスタブ化すると、スタブの適切なクリーンアップができず、後続のテストに影響が残るためです。しかし、この制限は method_defined? メソッドに基づいていたため、private および protected メソッドを検出できませんでした。
#293 で報告された問題では、prepend された private メソッドに対する any_instance スタブがテスト実行後も残存し、before(:context) ブロックで呼び出された際に "undefined method 'ensure_registered' for RSpec::Mocks::RootSpace" エラーが発生していました。この現象はテストの実行順序に依存するため、不安定なテスト失敗の原因となっていました。
技術的な変更
allow_no_prepended_module_definition_of メソッドの検出ロジックが、可視性に関わらずメソッドを検出できるように拡張されました。
変更前:
def allow_no_prepended_module_definition_of(method_name)
prepended_modules = RSpec::Mocks::Proxy.prepended_modules_of(@klass)
problem_mod = prepended_modules.find { |mod| mod.method_defined?(method_name) }
return unless problem_mod
AnyInstance.error_generator.raise_not_supported_with_prepend_error(method_name, problem_mod)
end
変更後:
def allow_no_prepended_module_definition_of(method_name)
prepended_modules = RSpec::Mocks::Proxy.prepended_modules_of(@klass)
problem_mod = prepended_modules.find do |mod|
MethodReference.method_defined_at_any_visibility?(mod, method_name)
end
return unless problem_mod
AnyInstance.error_generator.raise_not_supported_with_prepend_error(method_name, problem_mod)
end
MethodReference.method_defined_at_any_visibility? メソッドは、public、protected、private のすべての可視性レベルでメソッドの存在を確認します。これにより、method_defined? では検出できなかった private メソッドと protected メソッドも正しく検出されるようになりました。
テストケースも拡張され、public メソッドだけでなく private/protected メソッドに対する any_instance スタブと receive_message_chain の両方が適切に制限されることを検証しています。追加されたテストは、prepend されたモジュールで定義された各可視性レベルのメソッドに対して、スタブ化の試みが /prepended module/ エラーで失敗することを確認します。
設計判断
既存の method_defined? ベースのチェックを、より包括的な method_defined_at_any_visibility? に置き換える方式が採用されました。
この変更は、検出ロジックの1行の置き換えのみで実装されています。新しいメソッドは MethodReference クラスに既存の機能として存在していたため、追加の実装は不要でした。この判断により、コードの変更を最小限に抑えながら、すべての可視性レベルでの一貫した動作を実現しています。
テストケースの構造も再編成され、可視性レベルごとにコンテキストを分けることで、各ケースでの期待動作が明確になりました。この構造化により、将来的なメソッド可視性の追加や変更に対しても、テストの拡張が容易になります。
まとめ
本PRは、メソッド検出ロジックを可視性非依存の実装に変更することで、prepend パターンを使用したコードでのテスト間の状態漏洩を防止しました。method_defined? から method_defined_at_any_visibility? への置き換えという最小限の変更で、private/protected メソッドに対する any_instance スタブの制限が完全に機能するようになり、テスト実行順序に依存する不安定な失敗が解消されます。