7
votes

I'm trying to write some unit tests for my flask app that uses OpenID for authentication. Since it seems like there's no way to log in the test client via OpenID (I asked this question but it hasn't received any responses: Flask OpenID unittest), I was thinking of overriding g.user in my test, so I tried the code snippet from http://flask.pocoo.org/docs/testing/#faking-resources-and-context and it works as expected.

Unfortunately, when using flask-login, g.user is overridden in the before_request wrapper that sets

g.user = current_user

current_user is anonymous, so my test case is broken. One fix is to execute the before_request wrapper code only when in test mode but it seems lame that you need to add test-specific logic in production code. I've tried messing around with the request context too but g.user still gets overridden eventually. Any ideas for a clean way to solve this?

2

2 Answers

0
votes

The official documentation has an example in "Faking Resources and Context":

You first need to make sure that g.user is only set if it does not exist yet. You can do this using getattr. Here's a slightly modified example:

@APP.before_request
def set_user():
    user = getattr(g, 'user', None)
    if user is None:
        g.user = concrete_implementation()
    return user

By using getattr we give ourselves the chance to "inject" something during testing. If we would not do this, we would overwrite the variable again with the concrete implementation after the unit-tests injected the value.

The next thing we do is hook into the appcontext_pushed event and set the g.user value with a testable value. This happens before the before_request hook. So by the time that is called getattr will return our test-value and the concrete implementation is skipped:

from contextlib import contextmanager
from flask import appcontext_pushed, g

@contextmanager
def user_set(app, user):
    def handler(sender, **kwargs):
        g.user = user
    with appcontext_pushed.connected_to(handler, app):
        yield

And with this little helper we can inject something whenever we need to use a testable value:

def test_user_me():
    with user_set(app, 'our-testable-user'):
        c = app.test_client()
        resp = c.get('/protected-resource')
        assert resp.data == '...'
-1
votes

Based on this other question in a Flask unit-test, how can I mock objects on the request-global `g` object? what worked for me is the following:

In my app I refactored the login logic that is contained in the before_request into a separate function. Then, I patched that function in my tests so that it returns the specific user I want to use for a bunch of tests. The before_request is still run with the tests but by patching the function it invokes I can now avoid the actual login process.

I am not sure it is the cleanest way but I think it is better than adding test-only logic to your before_request; it is just a refactoring.