"RuntimeError: working outside of application context" when unit testing with py.test

21,331

Solution 1

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.

Solution 2

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)

Solution 3

You can set app context manually:

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

with ctx:
    pass
Share:
21,331
John Hazen
Author by

John Hazen

Coding let's me be creative. I also enjoy creativity in music, woodworking, and growing things.

Updated on July 12, 2022

Comments

  • John Hazen
    John Hazen almost 2 years

    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.

  • matthewlent
    matthewlent about 9 years
    I needed to remove the .test_client() from my app to get this to work.
  • varela
    varela over 8 years
    You can use virtualenv with virtualenvwrapper to add folders to sys.path globally for the whole application and avoid doing this in code