Python · Testing with pytest · Expert
Testing Django Apps with pytest-django
Test Django views, models, and forms using pytest-django's built-in fixtures.
Quick topic start and explanations before exercises (exercises below):
Django Testing Patterns in Practice
#pytest-django Reference
#Exercises:
Test a Django view returns HTTP 200
#You have a Django view `task_list` at URL `/tasks/` that returns a JSON response with a `results` key containing a list of tasks. Set up `pytest.ini` with `DJANGO_SETTINGS_MODULE` and write a test using the built-in `client` fixture that: 1. Calls `GET /tasks/` 2. Asserts the status code is 200 3. Asserts the response JSON contains a `results` key 4. Asserts `results` is a list
# pytest.ini
# [pytest]
# DJANGO_SETTINGS_MODULE = myproject.settings
# tests/test_views.py
import pytest
@pytest.mark.django_db
def test_task_list_returns_200(client):
# call GET /tasks/ and assert status + structure
pass
Solution
# pytest.ini
# [pytest]
# DJANGO_SETTINGS_MODULE = myproject.settings
# tests/test_views.py
import pytest
@pytest.mark.django_db
def test_task_list_returns_200(client):
response = client.get('/tasks/')
assert response.status_code == 200
data = response.json()
assert 'results' in data
assert isinstance(data['results'], list)
Test model save and database persistence
#You have a `Task` model with fields `title` (CharField) and `completed` (BooleanField, default=False). Write two tests: 1. **test_default_completed_is_false** — create a `Task`, then retrieve it from the database by primary key and assert `completed` is `False`. 2. **test_task_can_be_completed** — create a `Task` with `completed=False`, set `completed=True`, call `.save()`, then call `.refresh_from_db()` and assert the value persisted.
import pytest
from myapp.models import Task
@pytest.mark.django_db
def test_default_completed_is_false():
# create and retrieve fresh from DB
pass
@pytest.mark.django_db
def test_task_can_be_completed():
# create, update, save, refresh, assert
pass
Solution
import pytest
from myapp.models import Task
@pytest.mark.django_db
def test_default_completed_is_false():
task = Task.objects.create(title='New task')
task_from_db = Task.objects.get(pk=task.pk)
assert task_from_db.completed is False
@pytest.mark.django_db
def test_task_can_be_completed():
task = Task.objects.create(title='Finish report', completed=False)
task.completed = True
task.save()
task.refresh_from_db()
assert task.completed is True
Test an authenticated view with force_login
#You have a view `my_tasks` at `/my-tasks/` decorated with `@login_required`. It returns only the tasks owned by the currently authenticated user as JSON `{'results': [...]}`. Write two tests: 1. **test_my_tasks_redirects_anonymous** — assert an unauthenticated GET returns 302. 2. **test_my_tasks_returns_only_owners_tasks** — create two users and one task per user. Log in as `user1` using `client.force_login()`. Assert status 200 and the response contains exactly the one task that belongs to `user1`.
import pytest
from myapp.models import Task
@pytest.mark.django_db
def test_my_tasks_redirects_anonymous(client):
pass
@pytest.mark.django_db
def test_my_tasks_returns_only_owners_tasks(client, django_user_model):
pass
Solution
import pytest
from myapp.models import Task
@pytest.mark.django_db
def test_my_tasks_redirects_anonymous(client):
response = client.get('/my-tasks/')
assert response.status_code == 302
@pytest.mark.django_db
def test_my_tasks_returns_only_owners_tasks(client, django_user_model):
alice = django_user_model.objects.create_user(username='alice', password='x')
bob = django_user_model.objects.create_user(username='bob', password='x')
Task.objects.create(title="Alice's task", owner=alice)
Task.objects.create(title="Bob's task", owner=bob)
client.force_login(alice)
response = client.get('/my-tasks/')
assert response.status_code == 200
results = response.json()['results']
assert len(results) == 1
assert results[0]['title'] == "Alice's task"
Override a Django setting for one test with the settings fixture
#You have a utility function: ```python # myapp/utils.py from django.conf import settings def get_max_tasks(): return getattr(settings, 'MAX_TASKS_PER_USER', 10) ``` Write two tests: 1. **test_get_max_tasks_with_override** — use the `settings` fixture to set `MAX_TASKS_PER_USER = 3` and assert `get_max_tasks()` returns `3`. 2. **test_get_max_tasks_default** — do not use the `settings` fixture; assert `get_max_tasks()` returns the default `10`. Both tests should pass regardless of execution order.
# tests/test_utils.py
import pytest
from myapp.utils import get_max_tasks
def test_get_max_tasks_with_override(settings):
pass
def test_get_max_tasks_default():
pass
Solution
# tests/test_utils.py
import pytest
from myapp.utils import get_max_tasks
def test_get_max_tasks_with_override(settings):
settings.MAX_TASKS_PER_USER = 3
assert get_max_tasks() == 3
def test_get_max_tasks_default():
assert get_max_tasks() == 10
Test a view function directly with RequestFactory
#You have the `task_list` view function (not the URL — the function itself). Write a test using the `rf` fixture (RequestFactory) that: 1. Creates two `Task` objects in the database. 2. Builds a GET request with `rf.get('/tasks/')`. 3. Calls the view function directly: `response = task_list(request)`. 4. Asserts status 200 and the JSON response contains exactly 2 tasks. Then write a second test for the authenticated `my_tasks` view. Since `rf` skips middleware, `request.user` is not set automatically — assign it manually.
import json
import pytest
from myapp.views import task_list, my_tasks
from myapp.models import Task
@pytest.mark.django_db
def test_task_list_with_rf(rf):
pass
@pytest.mark.django_db
def test_my_tasks_with_rf(rf, django_user_model):
pass
Solution
import json
import pytest
from myapp.views import task_list, my_tasks
from myapp.models import Task
@pytest.mark.django_db
def test_task_list_with_rf(rf):
Task.objects.create(title='First')
Task.objects.create(title='Second')
request = rf.get('/tasks/')
response = task_list(request)
assert response.status_code == 200
data = json.loads(response.content)
assert len(data['results']) == 2
@pytest.mark.django_db
def test_my_tasks_with_rf(rf, django_user_model):
user = django_user_model.objects.create_user(username='alice', password='x')
Task.objects.create(title="Alice's task", owner=user)
request = rf.get('/my-tasks/')
request.user = user
response = my_tasks(request)
assert response.status_code == 200
data = json.loads(response.content)
assert len(data['results']) == 1