[pytest-dev] Working with other dependency injection systems

Kai Groner kai at gronr.com
Thu Aug 13 23:47:32 CEST 2015


Hi holger,

Thanks for your response.  Sorry I haven't followed up sooner.

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.

> 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.


> > 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
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.python.org/pipermail/pytest-dev/attachments/20150813/6bd52c14/attachment.html>


More information about the pytest-dev mailing list