Question

I am writing a small REST service with flask:

from flask import Flask
app = Flask(__name__)

app.config['count']=0

@app.route("/count")
def get_count():
    app.config['count']+=1
    return str(app.config['count']-1)

@app.route("/reset")
def reset():
    app.config['count']=0
    return str(app.config['count'])

if __name__ == "__main__":
    app.run(debug = True)

Sequential calls to /count should return 0, 1, 2, .... Calling /reset should reset the counter.

In order to test it, I've followed the flask and nose tutorial::

#!/usr/bin/python

import flask_server as server
import unittest
from nose.tools import assert_equals

class TestUnitBase(unittest.TestCase):

    def setUp(self):
        print "Setting up!"
        server.app.testing=True
        self.app = server.app.test_client()

    def test_count_is_zero_in_new_server(self):
        """/count should return 0 for a new server"""
        response       = self.app.get("/count")
        data, status   = response.data, response.status
        assert_equals(int(data), 0)
        print data, status

    def test_count_is_zero_in_new_server_again(self):
        """Asserts that the uptime response contains "boot" and "test" elements."""
        response       = self.app.get("/count")
        data, status   = response.data, response.status
        assert_equals(int(data), 0)
        print data, status

if __name__ == '__main__':
    import nose
    nose.run(argv=[__file__, '--with-doctest', '-v', '-s'])

The methods are identical, and I expect both of them to pass, but:

 $ nosetests -vs location_update_server/server/test_flask_server.py
/count should return 0 for a new server ... Setting up!
0 200 OK
ok
Asserts that the uptime response contains "boot" and "test" elements. ... Setting up!
FAIL

======================================================================
FAIL: Asserts that the uptime response contains "boot" and "test" elements.
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/home/adam/vioozer/servers/location_update_server/server/test_flask_server.py", line 28, in test_count_is_zero_in_new_server_again
    assert_equals(int(data), 0)
AssertionError: 1 != 0

----------------------------------------------------------------------
Ran 2 tests in 0.025s

FAILED (failures=1)

The setUp method was indeed called twice, but the server was created only once. How can I set the test so that every call to setUp() would start a new server, with counter=0?

Was it helpful?

Solution

You need to create a new application for each test, so that each test starts sending request to a brand new server. By "application" I mean the instance of class Flask.

Unfortunately this is a non-trivial change. The basics are explained in the official documentation: Application Factories.

After you follow the instructions in the referenced page you will not be calling

app = Flask(__name__)

in the global scope anymore, instead you will have a create_app() function that returns this app object. Each test can make its own then. Any requests you send into a brand new app object will be the first.

The tricky part about switching to an app factory is that once you move the app creation into a function you cannot use app.route() anymore to define your routes, since app is not defined in the global scope anymore. The solution is to use a blueprint that is attached to the application at run-time.

As a side note, the standard practice of calling the test client self.app like this:

self.app = server.app.test_client()

is pretty confusing. Now you have two app things. I prefer to call it client, since that is what it is:

self.client = server.app.test_client()

Lastly, note that your REST API is not really RESTful due to the dependency on order of calls and this undeterministic behavior is actually causing your problem. Your /count route is not really representing a resource, it is representing an action. GET requests should be read-only, changes to a resource should normally be issued with a PUT request.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top