A couple of days ago I posted a long blog entry about how we (or at least I) tend to use the Zope Component Archictecture. I personally often use the ZCA to introduce dependency injection specifically for unit tests.
The types of dependency injection the ZCA allows for can be used far more generally than just for making unit testing easier. The ZCA works pretty well generally for unit testing dependency injection patterns. But in some cases ZCA assumptions may make the resulting tested code less understandable. In particular, if you put this code under test:
from zope.component import queryUtility from zope.interface import implements from mypackage.interfaces import ICatalogQuery class CatalogQuery(object): implements(ICatalogQuery) def __call__(self, context, **kw): """ Does something complicated """ query = CatalogQuery() def unit_under_test(context): query = queryUtility(ICatalogQuery, default=query) return query(context, a=1)
And then in the code doing the testing, you do this:
import unittest from zope.component import getSiteManager class Test(unittest.TestCase): def setUp(self): sm = getSiteManager() sm.__init__() def test_it(self): from mypackage import unit_under_test from zope.component import getUtility from mypackage.interfaces import ICatalogQuery class DummyQuery(object): def __call__(self, *arg, **kw): return  class DummyContext(object): pass query = DummyQuery() context = DummyContext() sm = getSiteManager() sm.registerUtility(query, ICatalogQuery) self.assertEqual(unit_under_test(context), )
You note above that the code under test uses a utility lookup to obtain the catalog query API. This is done purely for testing purposes; not for pluggability purposes. However, a casual reader of the above code may assume that the "ICatalogQuery" API is a ZCA "plug point", as advertised by its retrieval as a ZCA "utility". While it may be useful to have an API definition for ICatalogQuery, in most systems such an API is explicitly not pluggable. There's no expectation in most systems with a low-level API like this that an arbitrary user should be expected to be able to plug in a different implementation of ICatalogQuery at all. The getUtility code in the unit under test should not need to exist; it's a misdirection indicating that this is some sort of plug point that is meant to take alternate implementations.
This package is meant to be used instead of the ZCA for unit testing purposes when there is exactly one implementation of the dependency being stubbed out, and the code just needs to be able to find an alternate testing implementation when the system is under test.
Using repoze.depinj, the above code under test becomes:
from repoze.depinj import lookup class CatalogQuery(object): def __call__(self, context, **kw): """ Does something complicated """ query = CatalogQuery() def unit_under_test(context): query = lookup(query) return query(context, a=1)
And the code doing the testing becomes:
import unittest from repoze import depinj class Test(unittest.TestCase): def setUp(self): depinj.clear() def test_it(self): from mypackage import unit_under_test from mypackage import query class DummyQuery(object): def __call__(self, *arg, **kw): return  class DummyContext(object): pass dummy_query = DummyQuery() context = DummyContext() depinj.inject(dummy_query, query) self.assertEqual(unit_under_test(context), )
While the code is really no more readable in isolation, we have avoided needing to define an ICatalogQuery Zope interface for this interaction. We don't actually use a Zope interface at all.
Note that while we don't need Zope interfaces to document this behavior, we can still document the CatalogQuery API with a Zope interface if we want to. But because we wouldn't use that interface as an adapter marker, it would be impossible for someone to misunderstand as a ZCA plugpoint.
By the way, the answer to "why not monkeypatch instead?" is because I don't own the module-scope codepath . The answer to "why not supply an optional
unit_under_test keyword argument supplying the query implementation?" is because it makes generating documentation from source harder. It's also kinda fugly and doesn't account for callables that want to accept truly arbitrary