pytest-tornado
pytest-tornado is a plugin for the Pytest testing framework that simplifies testing asynchronous Tornado applications. It provides a set of fixtures and markers designed to integrate Tornado's asynchronous features seamlessly into pytest tests. The current version is 0.8.1, released on June 17, 2020, and the project has an infrequent release cadence.
Common errors
-
pytest hangs after tests complete
cause The Tornado IOLoop, especially one manually managed or running in a separate thread for a test server, was not properly shut down, preventing pytest from exiting.fixEnsure that any custom `tornado.ioloop.IOLoop` instances are explicitly stopped using `io_loop.stop()` and `io_loop.close()`. If the IOLoop is in a separate thread, make sure you are stopping the instance created and running within that specific thread. pytest-tornado's provided fixtures (e.g., `http_server`) should handle their IOLoop lifecycle automatically. -
RuntimeError: no current event loop in thread (or similar 'Future waiting for an event loop to be running')
cause This error arises when `async def` functions or `asyncio`-based code is executed in a test without an active `asyncio` event loop. This often happens when mixing `pytest-tornado`'s `@pytest.mark.gen_test` with `pytest-asyncio` or native `async/await` without proper event loop management.fixIf using `pytest-asyncio`, ensure its decorator (`@pytest.mark.asyncio`) is placed *outside* or manages the context for `@pytest.mark.gen_test` if both are applied. For simple native coroutine tests with Tornado 5.0+, consider if `pytest-tornasync` is a better fit as it handles `async def` more directly. -
Deadlock or tests hanging when using external browser automation (e.g., Splinter, Selenium)
cause External, blocking browser drivers run synchronously and can contend with the asynchronous `IOLoop` that the Tornado application is running on within the same test context, preventing the server from processing requests.fixRun the Tornado application in a separate thread or process that is distinct from the main test `IOLoop` where the blocking browser automation operates. This allows the server to respond independently of the blocking client.
Warnings
- gotcha When testing native `async def` coroutines (Python 3.5+, Tornado 5.0+), the `@pytest.mark.gen_test` decorator, which is designed for `tornado.gen.coroutine` style, might feel less intuitive. While it can still be used, alternatives like `pytest-tornasync` offer direct support for `async def` without this decorator.
- gotcha Combining `pytest-tornado` with `pytest-asyncio` can lead to issues if decorators are incorrectly ordered or event loops are mismanaged. The `asyncio` event loop might not run, causing tests to hang or fail with `RuntimeError: no current event loop in thread`.
- gotcha Tests can hang indefinitely if a Tornado `IOLoop` instance, especially one running in a separate thread or manually managed, is not explicitly shut down after test completion. `pytest-tornado` fixtures (`io_loop`, `http_server`) typically handle this automatically for tests using them.
Install
-
pip install pytest-tornado
Imports
- pytest.mark.gen_test
import pytest
Quickstart
import pytest
import tornado.web
import tornado.httpclient
class MainHandler(tornado.web.RequestHandler):
def get(self):
self.write("Hello, world")
application = tornado.web.Application([
(r"/", MainHandler),
])
@pytest.fixture
def app():
return application
@pytest.mark.gen_test
async def test_hello_world(http_client, base_url):
response = await http_client.fetch(base_url)
assert response.code == 200
assert response.body == b"Hello, world"
# To run: `pytest your_test_file.py`