I have some trouble with writing tests with AsyncHTTPTestCase for existing Tornado application that uses asyncio event loop. Here I prepare short model where I can reproduce issue:
app.py
from tornado.platform.asyncio import AsyncIOMainLoop
import asyncio
import tornado.web
class MainHandler(tornado.web.RequestHandler):
async def get(self, *args, **kwargs):
self.write("200 OK")
async def post(self, *args, **kwargs):
self.write("201 OK")
def make_app():
AsyncIOMainLoop().install() # here is how to asyncio loop installed in app I already have
return tornado.web.Application([
(r"/", MainHandler),
], debug=True)
def start_app():
app = make_app()
app.listen(8888)
loop = asyncio.get_event_loop()
loop.set_debug(True)
loop.run_forever()
start.py
#!/usr/bin/env python3
import app
if __name__ == "__main__":
app.start_app()
test_app.py
import json
from tornado.testing import AsyncHTTPTestCase
import app
class TestHelloApp(AsyncHTTPTestCase):
def get_app(self):
return app.make_app()
def test_get(self):
response = self.fetch('/')
self.assertEqual(response.code, 200)
self.assertEqual(response.body.decode(), '200 OK')
def test_post(self):
response = self.fetch('/', method="POST",
body=json.dumps({"key": "value"}))
self.assertEqual(response.code, 200)
self.assertEqual(response.body.decode(), '201 OK')
With that approach of installation asyncio loop application works fine (I mean I can do requests and I'm getting responses), but test like this failed with error:
======================================================================
FAIL: test_post (test_app.TestHelloApp)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/home/biceps/work/torn/.venv/lib/python3.6/site-packages/tornado/testing.py", line 380, in setUp
self._app = self.get_app()
File "/home/biceps/work/torn/test_app.py", line 8, in get_app
return app.make_app()
File "/home/biceps/work/torn/app.py", line 14, in make_app
tornado.platform.asyncio.AsyncIOMainLoop().install()
File "/home/biceps/work/torn/.venv/lib/python3.6/site-packages/tornado/ioloop.py", line 181, in install
assert not IOLoop.initialized()
AssertionError
----------------------------------------------------------------------
Ran 2 tests in 0.006s
FAILED (failures=1)
Seems like loop that was installed by AsyncIOMainLoop().install() command is not stopped between tests, first test passed OK, but second always failed.
When I moved AsyncIOMainLoop().install() to start_app() method - tests are passed OK, but I'm worrying about that during test I use one event loop, but in real running app I use asyncio loop.
So, against that code tests are passed OK:
from tornado.platform.asyncio import AsyncIOMainLoop
import asyncio
import tornado.web
class MainHandler(tornado.web.RequestHandler):
async def get(self, *args, **kwargs):
self.write("200 OK")
async def post(self, *args, **kwargs):
self.write("201 OK")
def make_app():
return tornado.web.Application([
(r"/", MainHandler),
], debug=True)
def start_app():
AsyncIOMainLoop().install()
app = make_app()
app.listen(8888)
loop = asyncio.get_event_loop()
loop.set_debug(True)
loop.run_forever()
Q: My question is - how to do write tests in that usecase correctly ? How to write tests with AsyncHTTPTestCase when Tornado app uses AsyncIOMainLoop ? Am I right with decision to make AsyncIOMainLoop().install() into start_app(), not in make_app() function ?
P.S. I've added self.io_loop.clear_instance() to tearDown() - it looks probably dirty but that works for case when AsyncIOMainLoop().install() called from make_app() code.
def tearDown(self):
self.io_loop.clear_instance()
super().tearDown()
According to documentation I need to install AsyncIOMainLoop before startup application, not when I'm making app. documentation
So now I'm sure that proper way is using AsyncIOMainLoop installation into start_app() code.
So now my pattern code looks like:
web1.py
test_app.py
This pattern works as well, and during tests AsyncIOMainLoop is used. So I can use libraries those use asyncio loop. In my example there is asyncio.sleep() for example.