pytest-tornasync
pytest-tornasync is a simple pytest plugin that provides helpful fixtures for testing Tornado (version 5.0 or newer) applications. It simplifies testing native Python 3.5+ coroutines by eliminating the need for decorators like `@pytest.mark.gen_test`. The current version is 0.6.0.post2, with the last release in July 2019, indicating a stalled release cadence and an 'at risk' maintenance status, although the plugin remains functional for its intended purpose.
Common errors
-
FixtureLookupError: tornado application fixture not found
cause The `pytest-tornasync` plugin could not find a pytest fixture named `app` that is expected to provide the `tornado.web.Application` instance for testing.fixCreate a fixture named `app` that returns your `tornado.web.Application`. Place this fixture in `conftest.py` or directly in your test file. ```python import pytest import tornado.web @pytest.fixture def app(): # Your Tornado application setup class MainHandler(tornado.web.RequestHandler): def get(self): self.write("Hello") return tornado.web.Application([(r"/", MainHandler)]) ``` -
RuntimeError: No current event loop in thread 'Thread-X' (or similar errors related to asyncio event loop)
cause Your asynchronous Tornado test code is attempting to interact with the `asyncio` event loop (e.g., awaiting a future) outside of a running event loop context, or the event loop provided by `pytest-tornasync` is not being properly utilized.fixEnsure your async test functions are properly defined with `async def` and receive the necessary fixtures (e.g., `io_loop`, `http_server_client`) provided by `pytest-tornasync` to operate within its managed event loop. Avoid explicit `asyncio.get_event_loop()` calls unless you are managing the loop lifecycle manually. -
TypeError: 'coroutine' object is not iterable (or similar errors when a test function is async but treated as sync)
cause Pytest is not recognizing your `async def` test function as an asynchronous test that needs to be run by an async-aware plugin. This often happens if `pytest-tornasync` is not correctly installed or enabled, or if another plugin is interfering.fixVerify that `pytest-tornasync` is correctly installed (`pip show pytest-tornasync`). Ensure no conflicting async plugins (like an older `pytest-tornado` that expects `@gen_test`) are enabled. Restart your pytest session to ensure plugin discovery.
Warnings
- breaking pytest-tornasync requires Tornado version 5.0 or newer and Python 3.5+. Older versions of Tornado or Python will not be supported and may lead to unexpected errors or incompatible behavior.
- gotcha The plugin automatically discovers fixtures. However, if the `app` fixture, which returns a `tornado.web.Application` instance, is not defined in your test suite, `pytest-tornasync` will raise a `FixtureLookupError: tornado application fixture not found` during test collection.
- gotcha Unlike `pytest-tornado`, `pytest-tornasync` is designed for plain native coroutine tests and does *not* require (or use) the `@pytest.mark.gen_test` decorator. Applying it might lead to unexpected behavior or redundant processing, as it is intended for a different async testing pattern.
- gotcha Async tests, especially those involving I/O or external services, can sometimes hang if the event loop is not correctly managed or if there are unhandled futures/tasks. This can manifest as `pytest` appearing to freeze indefinitely.
Install
-
pip install pytest-tornasync
Imports
- pytest.fixture
import pytest
- tornado.web.Application
import tornado.web
Quickstart
import pytest
import tornado.web
from tornado.testing import AsyncHTTPTestCase
# Define your Tornado application
class MainHandler(tornado.web.RequestHandler):
async def get(self):
self.write("Hello, world")
def make_app():
return tornado.web.Application([
(r"/", MainHandler),
])
# Define the 'app' fixture for pytest-tornasync
@pytest.fixture
def app():
return make_app()
# Write an async test using the http_server_client fixture
async def test_main_handler(http_server_client):
response = await http_server_client.fetch('/')
assert response.code == 200
assert response.body == b"Hello, world"