Fixtures, Mocks and Test Doubles
Fixtures
A fixture provides something a test needs, and can clean it up afterwards.
# tests/conftest.py
import pytest
from app import create_app
from app.extensions import db
@pytest.fixture
def app():
app = create_app("testing")
with app.app_context():
db.create_all()
yield app
db.session.remove()
db.drop_all()
@pytest.fixture
def client(app):
return app.test_client()
Any test that takes a client argument gets a fresh app and database. Fixtures in conftest.py are available to every test in that folder.
Factory fixtures
@pytest.fixture
def make_user(app):
def _make(email="user@example.com", **kwargs):
user = User(email=email, username=email.split("@")[0], **kwargs)
user.set_password("CorrectHorse9!")
db.session.add(user)
db.session.commit()
return user
return _make
def test_admin_sees_dashboard(client, make_user):
make_user("admin@example.com", role=UserRole.ADMIN)
Factories create exactly the data each test needs, keeping tests independent.
Mocks: replacing the outside world
Tests must not send real emails or charge real cards. unittest.mock replaces those calls:
from unittest.mock import patch
def test_signup_sends_verification_email(client):
with patch("app.routes.auth.send_verification_email") as send:
client.post("/auth/register", data={...})
send.assert_called_once()
Patch where the name is used, not where it is defined. If auth.py does from app.services.email import send_verification_email, patch app.routes.auth.send_verification_email.
Types of test double
| Double | What it does |
|---|---|
| Stub | returns canned data (fetch_rates always returns {"eur": 0.9}) |
| Mock | records calls so you can assert on them |
| Fake | a working lightweight version (an in-memory database, a fake payment gateway) |
Over-mocking makes tests pass while the real system is broken. Mock at the boundaries (network, email, payment, clock) and use real code for everything inside.
Controlling time
from freezegun import freeze_time
@freeze_time("2026-10-20 09:00:00")
def test_streak_continues_next_day():
...
Never let a test depend on the real current date; it will fail at midnight or on the 31st of a month.
Temporary files
pytest's built-in tmp_path fixture gives each test a fresh folder:
def test_writes_report(tmp_path):
out = tmp_path / "report.csv"
write_report(out)
assert out.read_text().startswith("id,email")
Check yourself
- Where should you patch a function that a module imported by name?
- When is a fake better than a mock?
- Why freeze time in tests?
ההתקדמות שלכם נשמרת בדפדפן הזה בלבד.
שליחה לבדיקה (10 נקודות)