pythonunittest2

How to get per-testcase scoping for dependency injected objects in Python?


I'm using python-inject, python 2.6 (with unittest2).

We have classes to test that use injection, and test-cases which also use the same values. We currently use inject.appscope, to "singletonize" the values. Otherwise they're instantiated per user.

import inject

class A(object):
    '''shared data between many parts'''
    ...

class Foo(object):
    '''uses a to do stuff'''

    a = inject.attr('a', A)

    def bar(self):
        self.a.doStuff()

class TestFoo(TestCase):
    a = inject.attr('a', A)

    def setUp(self):
        self.foo = Foo()

    def testBar(self):
        self.foo.bar()
        self.assertTrue(self.a.stuffWasDone())
        self.assertFalse(self.a.stuffWasDoneTwice())

    def testBarTwice(self):
        self.foo.bar()
        self.foo.bar()
        self.assertTrue(self.a.stuffWasDoneTwice())


injector = inject.Injector()
injector.bind(A, scope=inject.appscope)
inject.register(injector)

runTests()

Ideally, I'd like to reset A after each test run, to avoid cross-contamination. (Currently do something like A.reset() in tearDown()..)

inject.reqscope supports something like this (a locally scoped set of instances), but I don't really know where to call register() & unregister() (which resets the injection objet cache). Ot's too late in setUp() and tearDown(), since Foo.a may already be called in Foo.__init__() for each of them.

Any ideas on how to do this, or should I use a different approach?


Solution

  • You can use setUp and tearDown to register/unregister the scope, treating each test method invocation as a new "request".

    With the following changes your sample unit tests pass:

    class TestFoo(unittest2.TestCase):
    
        a = inject.attr('a', A)
    
        def __init__(self, *n, **kw):
            unittest2.TestCase.__init__(self, *n, **kw)
            self.unit_scope = inject.reqscope
    
        def setUp(self):
            self.foo = Foo()
            self.unit_scope.register()
    
        def tearDown(self):
            self.unit_scope.unregister()
    
        ...
    

    And bind your A instance using inject.reqscope:

    injector = inject.Injector()
    injector.bind(A, scope=inject.reqscope)