1
class Generator(Resource):

    @admin_required
    def get(self):
       pass

If I add the @admin_required decorator on my view, the unit test start failing, with the message:

RuntimeError: working outside of request context

Is there a way to mock it or bypass it for unit tests?

Here is the decorator:

def admin_required(func):
    """Requires App Engine admin credentials"""
    @wraps(func)
    def decorated_view(*args, **kwargs):
        if users.get_current_user():
            if not users.is_current_user_admin():
                abort(401)  # Unauthorized
            return func(*args, **kwargs)
        return redirect(users.create_login_url(request.url))
    return decorated_view
Houman
  • 64,245
  • 87
  • 278
  • 460
  • It looks like you can just patch `users.is_current_user_admin` (and possibly `users.get_current_user`) – mgilson Feb 03 '15 at 00:27
  • I work around the @login_required annotation in unit tests by setting the request.user -- perhaps you could do a similar thing by setting request.user to be an admin user? – Jessamyn Smith Feb 03 '15 at 00:28

1 Answers1

3

I'd just patch the users module:

from google.appengine.api import users

# ...

@mock.patch.object(users, 'get_current_user')
@mock.patch.object(users, 'is_current_user_admin', return_value=True)
def test_handler(mock_get_current_user, mock_is_current_user_admin):
    invoke_your_handler()
    # make assertions, etc.

If you want to go deeper, you could use that GAE testbed ... Unfortunately, the documentation here seems to be pretty poor and tricky to get right... Based on this answer, it looks like you need to create a testbed instance and then set the environment:

from google.appengine.ext import testbed

testbed = testbed.TestBed()
testbed.activate()
testbed.init_user_stub()
# Sets environment variables...
testbed.setup_env(
    user_email='hello@gmail.com',
    user_id='123456',
    user_is_admin='1',  # '1' is an admin, '0' is a non-admin.
    overwrite=True,
)

Now continue your test as usual. If you are using unittest for testing, you'd probably want to pack all of that fun stuff into your setUp method.

Also note, in order to avoid polluting your environment, you'll want to call testbed.deactivate() at the end of the test.

Community
  • 1
  • 1
mgilson
  • 300,191
  • 65
  • 633
  • 696
  • The mocking works great. Many thanks. The testbed is a better idea in this case, as I don't have to change each related unittest. It seems there is no `testbed.set_env()` but there is `testbed.setup_env()`, however it doesn't work and throws the same error message. Granted I used `hello@gmail.com` as email as well, which I believe should be fine to do. – Houman Feb 03 '15 at 10:23
  • Oops, sorry `setup_env` :-)... Without seeing the rest of your code it's hard to guess at what the problem is... Did you read through the answer I linked about using testbed? – mgilson Feb 03 '15 at 14:42
  • Right. +1 The solution was to add `overwrite=True` to the `setup_env` otherwise it had no effect. On another note, when you mentioned "pack all that fun stuff into your `setUp`", I remembered I tried this and failed to achieve it. You seem to know this very well, please have a look: http://stackoverflow.com/questions/28223321/how-to-move-the-mock-patch-to-the-setup – Houman Feb 03 '15 at 17:10
  • 1
    Updated this answer with `overwrite=True` and answered the other question as well. Hopefully that helps. – mgilson Feb 03 '15 at 19:32
  • Oh, and here the packing stuff into setUp is easier... You just add a testbed instance to the TestCase (`self.testbed = testbed.TestBed()`) in `setUp`. Then finish setting it up as we did above. Then make sure that `self.testbed.deactivate` is called after the tests by passing it to `addCleanup` or putting it in `tearDown`. – mgilson Feb 03 '15 at 20:49