Instruction file imported from CoffeePoweredComputers/purplex (
.github/instructions/tests.instructions.md). Copyright stays with the author.
Test Review Rules
1. Every test file must declare pytestmark
Unit tests and integration tests must be explicitly marked. Without django_db, pytest-django blocks database access and raises a clear error.
# Unit test file
pytestmark = [pytest.mark.unit, pytest.mark.django_db]
# Integration test file
pytestmark = [pytest.mark.integration, pytest.mark.django_db]
Flag any test file missing pytestmark.
2. Use factories, never Model.objects.create()
All test data must come from Factory Boy factories in tests/factories/__init__.py. If no factory exists for a model, add one.
# WRONG
user = User.objects.create_user(username="test", password="pass")
course = Course.objects.create(name="Test", slug="test")
# RIGHT
from tests.factories import UserFactory, CourseFactory
user = UserFactory()
course = CourseFactory()
3. Use conftest fixtures for common objects
Shared fixtures are defined in tests/conftest.py. Use them for standard objects like user, admin_user, course, problem_set, problem.
# WRONG — recreating what conftest already provides
@pytest.fixture
def my_user():
return UserFactory()
# RIGHT — use the shared fixture
def test_something(user, course):
...
4. CourseFactory does not accept instructor=
The Course.instructor FK was removed. Instructor assignment uses CourseInstructorFactory.
# WRONG — will raise TypeError
course = CourseFactory(instructor=user)
# RIGHT
course = CourseFactory()
CourseInstructorFactory(course=course, user=user, role="primary")
5. Mock Firebase via get_firebase_service()
Never call Firebase directly in tests. Mock the service factory.
# WRONG
from firebase_admin import auth
auth.delete_user(uid)
# RIGHT
@patch("purplex.users_app.utils.firebase.get_firebase_service")
def test_delete(mock_firebase):
mock_firebase.return_value = MagicMock()
...
6. Use force_authenticate for auth
# WRONG
client.credentials(HTTP_AUTHORIZATION="Bearer fake-token")
# RIGHT
api_client.force_authenticate(user=user)
7. Test file naming
Test files must follow test_<feature>.py convention. Flag files like tests_feature.py or feature_test.py.
8. Assert specific exceptions
# WRONG — too broad, hides bugs
with pytest.raises(Exception):
service.do_thing()
# RIGHT
with pytest.raises(NotFound, match="Course not found"):
service.do_thing()
9. No shared state between tests
Each test must create its own data via factories or fixtures. Never rely on data created by another test. No module-level model instances.
# WRONG — module-level state shared across tests
user = UserFactory()
class TestCourse:
def test_a(self):
course = CourseFactory() # uses module-level user implicitly
# RIGHT
class TestCourse:
def test_a(self):
user = UserFactory()
course = CourseFactory()
10. Privacy and FERPA test patterns
- Use
UserConsentFactoryandAgeVerificationFactoryfor consent tests - For FERPA tests, set
directory_info_visibleon the user's profile:
# Set up FERPA visibility
user = UserFactory()
user.profile.directory_info_visible = True
user.profile.save()
- Use fixtures from conftest:
user_consent,age_verification,minor_user,child_user,nominee
11. del on MagicMock is intentional
del mock.attribute is a valid pattern for simulating an object that lacks a specific attribute. MagicMock.__delattr__ removes the name from the mock's internal state — after deletion, hasattr(mock, 'attribute') returns False and mock.attribute raises AttributeError. Do not flag this as unreliable.
# This is correct — exercises the "no segmentation" branch
submission = MagicMock()
del submission.segmentation
assert not hasattr(submission, 'segmentation') # True