This article explains how to set up a test database using fixtures and manage dependencies for efficient testing.
In this lesson, we will learn how to set up a test database using fixtures and configure one fixture to depend on another. This approach enables you to create a fixture that returns a database session (or database object) and then pass that session fixture to another fixture that provides a configured TestClient. Dependency chaining like this simplifies database manipulation and client usage in your tests.
Using fixture dependency helps separate concerns by allowing one fixture to manage the database session while another focuses on providing an HTTP client. This setup ensures that tests run against a freshly configured database, improving test reliability.
In this example, the fixture returns our client. However, if you need direct access to the database object, you can separate the responsibilities by creating a dedicated fixture (named session) to set up the database and return a database session. Then, pass this session fixture to the TestClient fixture.
Creating a Session Fixture and Chaining Dependencies
Below is an updated version where we define the session fixture for managing database setup (dropping and creating tables) and then configure the TestClient fixture to use this session:
Copy
Ask AI
@pytest.fixturedef session(): Base.metadata.drop_all(bind=engine) Base.metadata.create_all(bind=engine) db = TestingSessionLocal() try: yield db finally: db.close()@pytest.fixturedef client(): yield TestClient(app)def test_test_root(client): # This test uses the client fixture which initializes the database session res = client.get("/")
At this point, the test framework ensures that whenever the client fixture is used, the session fixture runs first. This guarantees a fresh database setup and an available session before tests execute. You can also access the session directly in your tests if necessary.Here’s an enhanced version that demonstrates passing the session fixture into the TestClient fixture and using it in a test:
Copy
Ask AI
@pytest.fixturedef session(): Base.metadata.drop_all(bind=engine) Base.metadata.create_all(bind=engine) db = TestingSessionLocal() try: yield db finally: db.close()@pytest.fixturedef client(session): yield TestClient(app)def test_root(client): res = client.get("/") print(res.json().get("message")) assert res.json().get("message") == "Hello World"
Next, modify the client fixture to override the database dependency correctly. Instead of yielding a hard-coded database object, define an inner function override_get_db that yields the session fixture. Apply this override before returning the TestClient:
With this setup, every time a test uses the client fixture, the session fixture is invoked first. This guarantees that the TestClient has access to a fresh database session, allowing direct database queries such as session.query(models.Post) when required.For example, to access the database session separately from the client, include the session fixture in your test parameters:
Organizing Database Configuration into a Separate File
After verifying that your tests work as expected (for example, running pytest tests/test_calculations.py in the terminal), consider cleaning up your test files by moving database-specific code and fixtures into a separate file (such as database.py) within your test directory.An example of what the database.py file might look like:
After moving the database configuration and fixtures into database.py, your test file (e.g., test_users.py) becomes more organized. It now only needs to import the necessary fixtures and define the test cases. For instance:
After saving your changes, running your tests should confirm that both tests pass successfully. This organized setup not only cleans up your test files but also ensures that the database is properly configured and available during testing.
If you encounter warnings such as “is deprecated since Python 3.8, use ‘async def’ instead,” review any legacy non-fixture code. Since table creation and session management are now fully handled within the fixtures, these warnings should no longer apply.
This concludes our lesson on setting up a test database with fixtures. By leveraging fixture dependency, we efficiently manage both the TestClient and the database session, simplifying testing and ensuring a reliable, isolated test environment.For additional information and advanced testing strategies, please refer to the FastAPI Testing Documentation.