From fls
Write pytest tests. Use when implementing features, fixing bugs, or when the user mentions testing, TDD, or pytest
npx claudepluginhub preludetech/django-craftThis skill is limited to using the following tools:
This Skill helps implement features and fix bugs using Test-Driven Development, following the Red-Green-Refactor cycle.
Implements TDD for Django apps using pytest-django, factory_boy, mocking, coverage.py, and DRF API testing. Use when writing Django apps, APIs, models, views, serializers, or test setups.
Guides TDD in Django with pytest-django, factory_boy, fixtures, mocking, coverage for testing models, views, serializers, DRF APIs.
Implements pytest-based testing strategies for Python with fixtures, mocking, parameterization, TDD, unit/integration tests, and CI/CD. Use for writing tests, test suites, or debugging failures.
Share bugs, ideas, or general feedback.
This Skill helps implement features and fix bugs using Test-Driven Development, following the Red-Green-Refactor cycle.
Use this Skill when:
pyproject.tomlconfig.settings_devpytest path/to/test_file.py::test_nameUse factory_boy factories for all test data creation. Never use .objects.create() directly.
factories.py (e.g., from freedom_ls.accounts.factories import UserFactory)freedom_ls/<app_name>/tests/test_<module>.py@pytest.mark.django_db for database testsmock_site_context fixture for site-aware models — never manually set site.objects.create() directlyreverse() for URLs, never hardcode@pytest.mark.django_db
def test_model_method(mock_site_context):
"""Test a specific model method."""
instance = MyModelFactory(field1="value")
result = instance.some_method()
assert result == expected_value
@pytest.mark.django_db
def test_endpoint(client, mock_site_context):
"""Test endpoint returns expected response."""
user = UserFactory()
client.force_login(user)
response = client.get(reverse('app:endpoint'))
assert response.status_code == 200
assert response.context['key'] == expected_data
def test_utility_function():
"""Test utility function."""
result = utility_function(input_value)
assert result == expected_output
@pytest.mark.django_db
def test_requires_field(mock_site_context):
"""Test that field is required."""
with pytest.raises(IntegrityError):
MyModelFactory(required_field=None)
Every test must justify its existence. A test has value when it catches real bugs, documents important behaviour, or protects against meaningful regressions. Tests that merely exercise code without asserting anything interesting are noise.
CharField stores strings, that ForeignKey creates a column, or that reverse() resolves a URL you just defined. Trust the framework.MyModelFactory() succeeds, you already know the model works.assert response.status_code == 200 when the view does complex work)@pytest.mark.parametrize@pytest.mark.django_db for database testsmock_site_context for site-aware modelsassert result == [] NOT assert type(result) is listpytest.raises for exceptionsAlways use mock_site_context fixture:
@pytest.mark.django_db
def test_creation(mock_site_context):
instance = MyModelFactory()
assert instance.site is not None
Don't manually set site - mock_site_context handles it automatically.
Avoid repetition:
IMPORTANT: Do not forget the refactor step. All tests should be clean and DRY!
freedom_ls/<app_name>/tests/test_<module>.pyCover these for each feature:
When testing validation logic: Test the happy and unhappy path. Don't just test things that will pass, assert that validation FAILS when it is supposed to
Never test that a hardcoded configuration value is what it is meant to be. Eg never say assert config.hardcoded_value == [whatever] or assert "something" in config.hardcoded_value
Never test trivial model instance creation. Eg never test that default values are as they should be, or that passed in values are saved unless the model is meant to do something unusual. Assume Django's model implementation works, don't waste time testing it.
Never test trivial Admin panel functionality. Assume Django's Admin interface just works, don't write tests that assert that the admin shows up exactly as it was configured because it will always do that. If you have done something unusual in the admin then test that.