8

I'm trying to migrate to py.test for the ease of use and auto-discovery of tests. When I run my tests with unittest, the test works fine. When I run the test under py.test, I get RuntimeError: working outside of application context.

Here's the test code (test_app.py):

import unittest

from app import app

class TestAPILocally(unittest.TestCase):
    def setUp(self):
        self.client = app.test_client()

    def testRoot(self):
        retval = self.client.get('/').data
        self.assertTrue('v1' in retval)

if __name__ == '__main__':
    unittest.main()

And here's the stripped down file I'm testing (app.py):

from flask import Flask
from flask.ext.restful import Api, Resource

class APIListAPI(Resource):
    def get(self):
        return ['v1']

app = Flask(__name__)
api = Api(app)
api.add_resource(APIListAPI, '/')

As you can see, this is very similar to the docs on the flask site: the testing skeleton, and indeed, when I run it with unittest, it succeeds:

$ python tmp1/test_app.py 
.
----------------------------------------------------------------------
Ran 1 test in 0.115s

OK
$ 

But, when I test with py.test, it fails:

$ ./py.test tmp1/test_app.py
=================== test session starts =========================
platform sunos5 -- Python 2.7.5 -- py-1.4.22 -- pytest-2.6.0
collected 1 items

tmp1/test_app.py F

========================= FAILURES ==============================
_________________ TestAPILocally.testRoot _______________________

self = <tmp1.test_app.TestAPILocally testMethod=testRoot>

    def testRoot(self):
>       retval = self.client.get('/').data

tmp1/test_app.py:10:
 _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
werkzeug/test.py:762: in get
    return self.open(*args, **kw)
flask/testing.py:108: in open
    follow_redirects=follow_redirects)
werkzeug/test.py:736: in open
    response = self.run_wsgi_app(environ, buffered=buffered)
werkzeug/test.py:659: in run_wsgi_app
    rv = run_wsgi_app(self.application, environ, buffered=buffered)
werkzeug/test.py:855: in run_wsgi_app
    app_iter = app(environ, start_response)
tmp1/flask/app.py:1836: in __call__
    return self.wsgi_app(environ, start_response)
tmp1/flask/app.py:1820: in wsgi_app
    response = self.make_response(self.handle_exception(e))
flask_restful/__init__.py:256: in error_router
    if self._has_fr_route():
flask_restful/__init__.py:237: in _has_fr_route
    if self._should_use_fr_error_handler():
flask_restful/__init__.py:218: in _should_use_fr_error_handler
    adapter = current_app.create_url_adapter(request)
werkzeug/local.py:338: in __getattr__ 
    return getattr(self._get_current_object(), name)
werkzeug/local.py:297: in _get_current_object
    return self.__local()
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

    def _find_app():
        top = _app_ctx_stack.top
        if top is None:
>           raise RuntimeError('working outside of application context')
E           RuntimeError: working outside of application context

flask/globals.py:34: RuntimeError
================ 1 failed in 1.02 seconds ======================

Now, it turns out, I can make this test pass just by doing this:

$ rm tmp1/__init__.py

And make it fail again by doing this:

$ touch tmp1/__init__.py

So, is there some difference between the way that unittest and py.test handles files in modules? It seems very strange that it breaks enough to make Flask complain, as I clearly am in an app context calling app.test_client().get(). Is this expected behavior, or should I file a bug against py.test?

In case it's relevant, the reason I'm executing the tests from the parent directory is because I don't have the ability to add modules to site-packages, so I'm initiating all my code from the parent directory, where I've installed Flask, py.test, etc.

Edit: Solved. It was an installation problem. Adding pythonpath tag, since that was the solution.

Gianfranco P.
  • 10,049
  • 6
  • 51
  • 68
John Hazen
  • 1,296
  • 1
  • 8
  • 19
  • 1
    Can't reproduce. Can you please add used versions, file structure, and command to run test? – tbicr Jul 22 '14 at 06:17
  • @tbicr - Thanks. I looked into it further with a virtual machine, and it doesn't happen if I pip install flask. I'll put up an answer in a minute explaining what went wrong. – John Hazen Jul 22 '14 at 17:18

3 Answers3

11

Not directly answer to TS question, but mostly for 'application context' error.

Adding pushing and poping context in setUp and tearDown functions should help with this error:

def setUp(self):
    self.app_context = app.app_context()
    self.app_context.push()

def tearDown(self):
    self.app_context.pop()

You can find more info on flask context there:

also in this awesome article by Daniel Kronovet:

PS If you planning to use url_for in tests, additional configuration required:

@classmethod
def setUpClass(cls)
    app.config['SERVER_NAME'] = 'localhost:5000'

Example

class ViewsTestCase(unittest.TestCase):
    @classmethod
    def setUpClass(cls):
        app.config['SERVER_NAME'] = 'localhost:5000'
        cls.client = app.test_client()

    def setUp(self):
        self.app_context = app.app_context()
        self.app_context.push()

    def tearDown(self):
        self.app_context.pop()

    def test_view_should_respond(self):
        r = self.client.get(url_for("index"))
        self.assertEqual(r.status_code, 200)
MadisonTrash
  • 5,444
  • 3
  • 22
  • 25
8

You can set app context manually:

app = Flask(__name__)
ctx = app.app_context()
ctx.push()

with ctx:
    pass
Vayn
  • 2,507
  • 4
  • 27
  • 33
4

In case it's relevant, the reason I'm executing the tests from the parent directory is because I don't have the ability to add modules to site-packages, so I'm initiating all my code from the parent directory, where I've installed Flask, py.test, etc.

It turns out that this was very relevant. Because my code would eventually be run by a daemon, I wasn't sure I could depend on PYTHONPATH, and I didn't want to change sys.path at the top of each file. So, I was installing the package tarballs, and adding symlinks where necessary to make the packages importable.

It turns out the structure required to make the unittest code find flask was:

./Flask-0.10.1/
./flask -> Flask-0.10.1/flask/
./tmp1/flask -> ../Flask-0.10.1/flask/

The last line was required by unittest, and it was that symlink that broke py.test.

When I just put the packages in their own directory, and set the PYTHONPATH, everything works. If I can't control the environment to use PYTHONPATH for the daemon, I'll bite the bullet, and add the sys.path modication everywhere.

So, bottom line is, it was an installation problem.

John Hazen
  • 1,296
  • 1
  • 8
  • 19
  • You can use virtualenv with virtualenvwrapper to add folders to sys.path globally for the whole application and avoid doing this in code – varela Nov 02 '15 at 10:36