diff --git a/tests/test_injection.py b/tests/test_injection.py index 84fb4605..1b99e668 100644 --- a/tests/test_injection.py +++ b/tests/test_injection.py @@ -29,6 +29,7 @@ NonCallableProviderError, UndeclaredProvidedTypeError, ) +from scrapy_poet.page_input_providers import ItemProvider def get_provider(classes, content=None): @@ -270,6 +271,62 @@ def callback( "d": ClsNoProviderRequired, } + @inlineCallbacks + def test_build_callback_dependencies_minimize_provider_calls(self): + """Test that build_callback_dependencies does not call any given + provider more times than it needs when one provided class is requested + directly while another is a page object dependency requested through + an item.""" + + class ExpensiveDependency1: + pass + + class ExpensiveDependency2: + pass + + class ExpensiveProvider(PageObjectInputProvider): + provided_classes = {ExpensiveDependency1, ExpensiveDependency2} + + def __call__(self, to_provide): + if to_provide != self.provided_classes: + raise RuntimeError( + "The expensive dependency provider has been called " + "with a subset of the classes that it provides and " + "that are required for the callback in this test." + ) + return [cls() for cls in to_provide] + + @attr.define + class MyItem: + pass + + @attr.define + class MyPage(ItemPage[MyItem]): + expensive: ExpensiveDependency2 + + def callback( + expensive: ExpensiveDependency1, + item: MyItem, + ): + pass + + providers = { + ItemProvider: 1, + ExpensiveProvider: 2, + } + injector = get_injector_for_testing(providers) + injector.registry.add_rule(ApplyRule("", use=MyPage, to_return=MyItem)) + response = get_response_for_testing(callback) + + # This would raise RuntimeError if expectations are not met. + kwargs = yield from injector.build_callback_dependencies( + response.request, response + ) + + # Make sure the test does not simply pass because some dependencies were + # not injected at all. + assert set(kwargs.keys()) == {"expensive", "item"} + class Html(Injectable): url = "http://example.com"