{"id":8543,"library":"pytest-tornado","title":"pytest-tornado","description":"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.","status":"active","version":"0.8.1","language":"en","source_language":"en","source_url":"https://github.com/eugeniy/pytest-tornado","tags":["pytest","tornado","async","asynchronous","testing","plugin","web"],"install":[{"cmd":"pip install pytest-tornado","lang":"bash","label":"Install stable version"}],"dependencies":[{"reason":"Core testing framework","package":"pytest","version":">=3.6"},{"reason":"Asynchronous web framework","package":"tornado","version":">=4.1"},{"reason":"Build and distribution utility","package":"setuptools","version":""}],"imports":[{"note":"Used as a decorator for Tornado coroutine tests. No direct imports from `pytest_tornado` are typically needed as it provides fixtures.","symbol":"pytest.mark.gen_test","correct":"import pytest"}],"quickstart":{"code":"import pytest\nimport tornado.web\nimport tornado.httpclient\n\nclass MainHandler(tornado.web.RequestHandler):\n    def get(self):\n        self.write(\"Hello, world\")\n\napplication = tornado.web.Application([\n    (r\"/\", MainHandler),\n])\n\n@pytest.fixture\ndef app():\n    return application\n\n@pytest.mark.gen_test\nasync def test_hello_world(http_client, base_url):\n    response = await http_client.fetch(base_url)\n    assert response.code == 200\n    assert response.body == b\"Hello, world\"\n\n# To run: `pytest your_test_file.py`","lang":"python","description":"This quickstart demonstrates how to test a simple Tornado web application using `pytest-tornado`. It defines a Tornado application, provides it via a `pytest` fixture named `app`, and then uses the `@pytest.mark.gen_test` marker along with `http_client` and `base_url` fixtures to perform an asynchronous HTTP request and assert the response."},"warnings":[{"fix":"For native `async def` tests, consider `pytest-tornasync` or ensure `@pytest.mark.gen_test` is correctly applied. For complex scenarios, you might need to combine it with `pytest-asyncio` (see next warning).","message":"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.","severity":"gotcha","affected_versions":"All versions with Tornado >= 5.0"},{"fix":"If using both, ensure the `@pytest.mark.asyncio` decorator, if needed to manage the event loop, wraps `@pytest.mark.gen_test` for the `asyncio` loop to be properly initiated. Alternatively, streamline your setup to primarily use one async plugin if possible, or ensure explicit `IOLoop` management if mixing concerns.","message":"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`.","severity":"gotcha","affected_versions":"All versions when used with `pytest-asyncio`"},{"fix":"Ensure any manually created `IOLoop` instances are properly stopped (e.g., `io_loop.stop()`, `io_loop.close()`) in test teardown or fixture finalizers. If the `IOLoop` is in another thread, ensure you stop the *correct* `IOLoop` instance from that thread.","message":"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.","severity":"gotcha","affected_versions":"All versions, particularly with custom `IOLoop` management"}],"env_vars":null,"last_verified":"2026-04-16T00:00:00.000Z","next_check":"2026-07-15T00:00:00.000Z","problems":[{"fix":"Ensure 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.","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.","error":"pytest hangs after tests complete"},{"fix":"If 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.","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.","error":"RuntimeError: no current event loop in thread (or similar 'Future waiting for an event loop to be running')"},{"fix":"Run 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.","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.","error":"Deadlock or tests hanging when using external browser automation (e.g., Splinter, Selenium)"}]}