[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