[pytest-dev] Working with other dependency injection systems

holger krekel holger at merlinux.eu
Mon Sep 7 10:01:43 CEST 2015


On Thu, Aug 13, 2015 at 17:47 -0400, Kai Groner wrote:
> Hi holger,
> 
> Thanks for your response.  Sorry I haven't followed up sooner.

same here :)

> On Thu, May 7, 2015 at 7:21 AM, holger krekel <holger at merlinux.eu> wrote:
> 
> > Hi Kai,
> >
> > On Thu, Apr 30, 2015 at 19:28 -0400, Kai Groner wrote:
> > > I'm trying to figure out how I can test a code base that uses an existing
> > > dependency injection system.  I've run into two problems, and I have
> > > solutions for each of them but I think maybe there is a better way, so
> > I'm
> > > looking for some advice.
> >
> > Could you provide a simple abstract example?
> > Here and also in the following i don't really understand the background.
> >
> 
> The DI system we are using is called jeni.  I'll try to keep things simple
> here so you don't need to know a lot about it, but here's the url.
> https://github.com/rduplain/jeni-python
> 
> This is untested code, that I hope will be illustrative.  If you think it
> would be helpful to run it, let me know and I will make sure it works.
> 
> We write code sort of like this:
> 
> @route('login', method='POST')
> @jeni.annotate
> def login(
>         username: 'form:username',
>         password: 'form:password',
>         user_lookup: 'user_lookup',
>         session_init: 'session_init'):
>     user = user_lookup(username)
>     if user is None:
>         raise ValueError
>     if not user.check_password(password):
>         raise ValueError
>     session_init(user)
> 
> 
> Here is an example of an injector prototyped with a trivial user_lookup
> implementation:
> 
> class Injector(jeni.Injector): pass
> 
> @Injector.factory
> def user_lookup_factory():
>     class User:
>         def __init__(self, username, password=None):
>             self.username, self.password = username, password
> 
>         def check_password(self, password):
>             return password == self.password
> 
>     def user_lookup(username):
>         return User(username, username)
> 
> 
> Calling this, looks like:
> 
> 
> with Injector() as inj:
>     inj.apply(login)
> 
> There is a partial application mechanism, that creates a wrapper that
> resolves injector bindings at call time.
> 
> @jeni.annotate
> def test_login(login: jeni.partial(login)):
> 
>     login(username='kai', password='kai')
> 
>     with raises(LookupError):
> 
>         login(username='kai', password='KAI')
> 
> 
> with Injector() as inj:
> 
>     inj.apply(test_login)
> 
> Because we need to write a lot of tests, I want to avoid writing the with
> block with every test.  I thought maybe I could use py.test hooks to do
> this.

Please checkout https://pytest.org/latest/plugins.html#hookwrapper-executing-around-other-hooks

It should be the right hook to systematically do your with-injector
logic.


> > The first problem is how do I override how a test using this injector is
> > > called?  I think I want to use the pytest_runtest_call hook, but it still
> > > tries to invoke the test without the injector.  My current solution is to
> > > use the experimental hookwrapper mechanism to replace item.obj with a
> > > partially bound version, then restore it afterward.
> >
> 
> Here my problem is that py.test looks for a ``login`` fixture and when it
> finds none it decides the test cannot proceed.

You probably need to provide this "login" fixture even if it's empty
and work is done in the hook above. not sure i get all your semantics
100 % though.

best,
holger


> > > The second problem I'm having is the py.test fixture system is trying to
> > > resolve arguments that are provided by this other injector.  How can I
> > tell
> > > it that some of these don't need to be provided?  My current solution is
> > to
> > > blind py.test with a wrapper function with a *a, **kw signature.  I use
> > > functools.wraps, so annotations are introspectable for the other
> > injector,
> > > and delete the __wrapped__ attribute to prevent py.test from
> > introspecting
> > > it.  Is there a nicer, and perhaps less blunt, way to influence the
> > funcarg
> > > fixture behaviors?  I've tried a couple things with the
> > > pytest_collection_modifyitems hook, but haven't gotten anything that
> > works
> > > yet.
> > >
> > > Other details to know about this injection system:
> > > - we want to build and teardown the injector with each test
> > > - we may want to configure the injector differently for some tests
> > > (possibly with fixture data from py.test)
> 
> 
> 
> Kai

-- 
about me:    http://holgerkrekel.net/about-me/
contracting: http://merlinux.eu


More information about the pytest-dev mailing list