Search code examples
pythonunit-testingtornado

How to write unit tests for python tornado application?


I want to try to write some code following TDD practice. I want to create simple app based on python's tornado framework. I was looking through the internet how people write tests for tornado and found something like this:

class TestSomeHandler(AsyncHTTPTestCase):
    def test_success(self):
        response = self.fetch('/something')
        self.assertEqual(response.code, 200)

Correct me if I'm wrong but it looks more like integration tests. Instead of it I'm trying to write simple unit test for some dummy handler. For example such one:

class SomeHandler(BaseHandler):
    @gen.coroutine
    def get(self):
        try:
            from_date = self.get_query_argument("from", default=None)
            datetime.datetime.strptime(from_date, '%Y-%m-%d')
        except ValueError:
            raise ValueError("Incorrect argument value for from_date = %s, should be YYYY-MM-DD" % from_date)

And test would look like:

class TestSomeHandler(AsyncHTTPTestCase):

    def test_no_from_date_param(self):
        handler = SomeHandler()
        with self.assertRaises(ValueError):
            handler.get()

I know I miss at get() application and request. Not handled yet how to create them.

But my question is, do people write tests for tornado like in first example or somebody calls handlers inside of app? What pattern to follow? It would be nice if somebody have relevant code to share.


Solution

  • The pattern with AsyncHTTPTestCase is used mostly because, it makes all the request stuff for you. Of course one can also use AsyncTestCase and handle it manually.

    AsyncTestCase example. Since it will be testing get method that is coroutine, we will use gen_test to make it a bit simpler. RequestHandler requires Application and HTTPRequest objects. Because we are not relaying of app's settings, ui_methods, etc. Application is a simple mock.

    from tornado.testing import AsyncTestCase, gen_test
    from tornado.web import Application
    from tornado.httpserver import HTTPRequest
    from unittest.mock import Mock
    
    class TestSomeHandler(AsyncTestCase):
    
        @gen_test
        def test_no_from_date_param(self):
            mock_application = Mock(spec=Application)
            payload_request = HTTPRequest(
                method='GET', uri='/test', headers=None, body=None
            )
            handler = SomeHandler(mock_application, payload_request)
            with self.assertRaises(ValueError):
                yield handler.get()
    

    IMHO it is up to you which pattern to use. I choose AsyncHTTPTestCase for http-verb methods (get, post and so on), because:

    • it is easier
    • consumer of this method is HTTP client, so asserts response code, body make a lot of sense
    • it prevents of over-complicated methods

    Of course rest of handler's methods tested with AsyncTestCase.