This article explains how to create a test user fixture to decouple login tests from external states, improving test modularity and reducing code duplication.
In this lesson, we demonstrate how to decouple the login user test from other tests by ensuring the test does not rely on external states. The solution is to create a fixture that sets up a test user before executing the login test.Below is the original login test code, which creates a user and then performs a login:
When running these tests, you might encounter an error similar to:
Copy
Ask AI
venv\lib\site-packages\aiofiles\os.py:10: DeprecationWarning: "@coroutine" decorator is deprecated since Python 3.8, use "async def" instead-- Docs: https://docs.pytest.org/en/stable/warnings.htmlFAILED tests/test_users.py::test_login_user -> assert 403 == 200
This error occurs because the login test expects a user to exist before running. The underlying issue is the need for a consistent fixture order or scope. Our goal is to centralize the user creation logic in a fixture, avoiding code repetition across multiple tests.Previously, the following snippet was repeated for creating a user and testing login:
Now, define a fixture that posts to the user creation endpoint. This fixture asserts successful user creation and returns the user data, including the password for subsequent login:
Copy
Ask AI
@pytest.fixturedef test_user(client): user_data = {"email": "[email protected]", "password": "password123"} res = client.post("/users/", json=user_data) assert res.status_code == 201 print(res.json()) new_user = res.json() # Include the password in the returned data for later authentication new_user["password"] = user_data["password"] return new_user
With the test user fixture in place, modify the login test to depend on both the client and the test_user fixture. This ensures that any changes in user creation details automatically propagate to the login test:
The client fixture depends on a session fixture. An example session fixture (typically defined elsewhere) might look like:
Copy
Ask AI
engine = create_engine(SQLALCHEMY_DATABASE_URL)TestingSessionLocal = sessionmaker( autocommit=False, autoflush=False, bind=engine)@pytest.fixture(scope="module")def session(): Base.metadata.drop_all(bind=engine) Base.metadata.create_all(bind=engine) db = TestingSessionLocal() try: yield db finally: db.close()
Similarly, the client fixture may override the dependency to use our test database session:
Copy
Ask AI
@pytest.fixture()def client(session): def override_get_db(): try: yield session finally: pass # The following setup is an example if you're using FastAPI: # app.dependency_overrides[get_db] = override_get_db # yield TestClient(app)
By refactoring our tests in this way, any changes to user credentials in the fixture will automatically reflect in the login test. This modular approach improves the maintainability and reliability of your test suite.