Python · Тестування з pytest · Експертний

Тестування Django-застосунків з pytest-django

5 завдань

Тестуйте Django views, моделі та форми за допомогою вбудованих фікстур pytest-django.

Тестування Django-застосунків з pytest-django

#
pytest-django з'єднує екосистему pytest з тестовою інфраструктурою Django. Він дає вам усі функції pytest -- фікстури, parametrize, мітки, плагіни -- водночас керуючи базою даних Django, тест-клієнтом і налаштуваннями. ## Встановлення та налаштування ```bash pip install pytest-django ``` Вкажіть pytest, де знаходяться ваші налаштування Django, додавши у `pytest.ini` в корені проекту: ```ini [pytest] DJANGO_SETTINGS_MODULE = myproject.settings ``` Без цього рядка pytest-django не може ініціалізувати Django, і будь-який імпорт з ваших моделей підніме `django.core.exceptions.AppRegistryNotReady`. ## Доступ до БД вимкнено за замовчуванням Найважливіше правило pytest-django: **доступ до бази даних вимкнено за замовчуванням.** Будь-який тест, що намагається звернутися до БД, підніме помилку, якщо ви явно не надасте доступ. Це навмисно -- тести, яким не потрібна БД, не повинні платити вартість або ризикувати побічними ефектами від тих, яким вона потрібна. Увімкніть доступ для кожного тесту через маркер `@pytest.mark.django_db`: ```python import pytest from myapp.models import Task @pytest.mark.django_db def test_task_creation(): task = Task.objects.create(title='Buy milk') assert Task.objects.count() == 1 assert task.title == 'Buy milk' ``` ## Ізоляція транзакцій Кожен тест `@pytest.mark.django_db` виконується всередині транзакції бази даних, яка **відкочується** після завершення тесту. Кожен тест починається з чистого стану і не може забруднювати інші тести через залишені рядки: ```python @pytest.mark.django_db def test_a(): Task.objects.create(title='Task from test_a') assert Task.objects.count() == 1 @pytest.mark.django_db def test_b(): assert Task.objects.count() == 0 # рядок test_a був відкочений ``` Для тестів, яким потрібні **реальні** зафіксовані транзакції -- код з хуками `on_commit()`, `select_for_update()` або читання БД між процесами -- використовуйте `@pytest.mark.django_db(transaction=True)`. Цей режим очищує БД після кожного тесту замість відкоту, що повільніше, але правильно для коду, чутливого до транзакцій. ## Основні вбудовані фікстури pytest-django надає кілька фікстур автоматично, впроваджених за іменем параметра: **`client`** -- Django `TestClient`, що надсилає HTTP-запити через повний стек Django (маршрутизація, проміжне ПЗ, вʼюхи) без запуску реального сервера: ```python @pytest.mark.django_db def test_task_list(client): response = client.get('/tasks/') assert response.status_code == 200 ``` **`rf`** -- `RequestFactory`, що будує об'єкти `HttpRequest` безпосередньо, оминаючи URL-маршрутизацію та все проміжне ПЗ. Використовуйте для тестування в'юх-функції у повній ізоляції: ```python from myapp.views import task_list @pytest.mark.django_db def test_task_list_view(rf): request = rf.get('/tasks/') response = task_list(request) assert response.status_code == 200 ``` **`admin_client`** -- як `client`, але попередньо автентифікований як Django-суперкористувач. Корисний для тестування адмін-в'юх без ручного створення облікових даних. **`settings`** -- дозволяє перевизначити будь-яке налаштування Django на час одного тесту. Зміни автоматично скасовуються після завершення тесту: ```python def test_custom_cache(settings): settings.CACHES = { 'default': {'BACKEND': 'django.core.cache.backends.locmem.LocMemCache'} } from django.core.cache import cache cache.set('key', 'value', 30) assert cache.get('key') == 'value' ``` **`django_user_model`** -- повертає клас моделі User, налаштований у `AUTH_USER_MODEL`. Завжди надавайте перевагу цьому над прямим імпортом `User`, щоб ваші тести працювали з кастомними моделями користувачів: ```python @pytest.mark.django_db def test_user_creation(django_user_model): user = django_user_model.objects.create_user(username='alice', password='x') assert user.username == 'alice' ``` ## Надання доступу до БД фікстурам Коли фікстурі потрібно звернутися до бази даних, додайте вбудовану фікстуру `db` як параметр. Це еквівалент `@pytest.mark.django_db` на рівні фікстури: ```python @pytest.fixture def sample_task(db): from myapp.models import Task return Task.objects.create(title='Fixture task') @pytest.mark.django_db def test_task_is_incomplete(sample_task): assert sample_task.completed is False ``` Якщо сам тест має `@pytest.mark.django_db`, його фікстури автоматично успадковують доступ до БД -- вам не потрібно додавати `db` до кожної фікстури. Параметр `db` потрібен головним чином, коли фікстура знаходиться в `conftest.py` і використовується в кількох тестових файлах, де деякі тести можуть не мати маркера. ## Тестування email через `mailoutbox` Фікстура `mailoutbox` перехоплює всі вихідні Django-email, надіслані під час тесту. Вона автоматично перемикає email-бекенд на in-memory бекенд `locmem` -- не потрібно вручну перевизначати `settings.EMAIL_BACKEND`: ```python import pytest @pytest.mark.django_db def test_welcome_email_is_sent(mailoutbox, django_user_model): django_user_model.objects.create_user(username='alice', password='x') assert len(mailoutbox) == 1 msg = mailoutbox[0] assert msg.subject == 'Welcome, alice!' assert '[email protected]' in msg.to ``` `mailoutbox` -- це список об'єктів `django.core.mail.EmailMessage`. Кожне повідомлення надає `subject`, `body`, `from_email`, `to`, `cc`, `bcc` та `attachments`. Список автоматично скидається до порожнього перед кожним тестом -- не потрібен ручний `mail.outbox.clear()`. Альтернатива -- встановити `settings.EMAIL_BACKEND = 'django.core.mail.backends.locmem.EmailBackend'` у тесті і читати `django.core.mail.outbox` безпосередньо. Обидва підходи працюють, але `mailoutbox` є ідіоматичним pytest-django: без імпортів, без очищення і з гарантованою ізоляцією між тестами. ## Маркер на рівні класу через `pytestmark` Повторення `@pytest.mark.django_db` на кожному методі тестового класу -- це зайвий шум. Використовуйте `pytestmark` для застосування маркера до цілого класу або модуля: ```python import pytest from myapp.models import Task class TestTaskModel: pytestmark = pytest.mark.django_db # застосовується до кожного методу в цьому класі def test_creation(self): task = Task.objects.create(title='Buy milk') assert Task.objects.count() == 1 def test_default_completed(self): task = Task.objects.create(title='New task') assert task.completed is False ``` На рівні модуля `pytestmark` позначає кожну тест-функцію у файлі: ```python # tests/test_models.py import pytest pytestmark = pytest.mark.django_db # кожна функція в цьому модулі отримує доступ до БД ``` `pytestmark` -- це стандартна конвенція pytest, не специфічна для pytest-django. Вона працює з будь-яким маркером і будь-яким scope, що має сенс для вашої організації коду -- `pytestmark` на рівні модуля поширений для виділених файлів тестів моделей. ## Цикл життя тестової БД і `--reuse-db` За замовчуванням pytest-django створює свіжу тестову базу даних на початку кожного запуску `pytest` і знищує її після завершення. Для проектів з багатьма міграціями це додає значний час запуску при кожному запуску. Прапор `--reuse-db` зберігає тестову базу між запусками. При наступних запусках pytest-django перевіряє незастосовані міграції і застосовує лише нові: ```bash pytest --reuse-db # перший запуск: створює БД; наступні запуски: повторно використовує pytest --create-db # примусове перестворення незалежно від поточного стану ``` Типове налаштування для великих проектів: ```ini [pytest] DJANGO_SETTINGS_MODULE = myproject.settings addopts = --reuse-db ``` Коли ви знаєте, що схема кардинально змінилася (міграції злиті, перейменовані): ```bash pytest --create-db ``` `--reuse-db` є безпечним з дефолтним режимом `transaction=False`, оскільки кожен тест все одно відкочує власні зміни всередині транзакції бази даних. Постійна база даних зберігає лише схему та будь-які початкові дані, завантажені на початку сеансу -- тести не можуть постійно записувати рядки в неї. **Виняток:** тести з позначкою `transaction=True` фіксують дані в базі даних. У поєднанні з `--reuse-db` зафіксовані рядки з одного сеансу переживають у наступний. Або уникайте поєднання `transaction=True` з `--reuse-db`, або додайте session-scoped teardown-фікстуру, що видаляє зафіксовані рядки. ## `live_server` -- тести з реальним HTTP-сервером Фікстура `live_server` запускає реальний WSGI-сервер на випадковому порту і передає об'єкт, атрибут `.url` якого є базовим URL сервера: ```python @pytest.mark.django_db(transaction=True) def test_homepage_over_http(live_server): import urllib.request resp = urllib.request.urlopen(f'{live_server.url}/') assert resp.status == 200 ``` `live_server` завжди вимагає `transaction=True`. Сервер виконується в окремому потоці -- без реально зафіксованих рядків серверний потік не може бачити дані, створені у відкоченій транзакції тестового потоку. Використовуйте `live_server` для тестів автоматизації браузера (Selenium, Playwright) або коли потрібно перевірити, що повний WSGI-стек (сервер, проміжне ПЗ, в'юхи) працює наскрізь по реальному TCP-з'єднанню. Для всіх інших тестів в'юх і API фікстура `client` (яка запускає застосунок у тому самому процесі) є простішою і швидшою.

Шаблони тестування Django на практиці

#
## Застосунок під тестом Усі приклади використовують невеликий Django-застосунок з таким налаштуванням: ```python # myapp/models.py from django.db import models from django.contrib.auth.models import User class Task(models.Model): title = models.CharField(max_length=200) completed = models.BooleanField(default=False) owner = models.ForeignKey( User, on_delete=models.CASCADE, null=True, blank=True ) ``` ```python # myapp/views.py from django.http import JsonResponse from django.contrib.auth.decorators import login_required from .models import Task def task_list(request): tasks = list(Task.objects.values('id', 'title', 'completed')) return JsonResponse({'results': tasks}) @login_required def my_tasks(request): tasks = list(Task.objects.filter(owner=request.user).values('id', 'title')) return JsonResponse({'results': tasks}) ``` ```python # myapp/urls.py from django.urls import path from . import views urlpatterns = [ path('tasks/', views.task_list), path('my-tasks/', views.my_tasks), ] ``` ## Тестування в'юх з `client` ```python 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) @pytest.mark.django_db def test_task_list_shows_created_tasks(client): from myapp.models import Task Task.objects.create(title='Buy groceries') Task.objects.create(title='Write tests') response = client.get('/tasks/') results = response.json()['results'] assert len(results) == 2 titles = [t['title'] for t in results] assert 'Buy groceries' in titles ``` ## Тестування збереження моделі та отримання з БД Після виклику `.save()` або `.create()` завжди отримуйте свіжий екземпляр з бази даних, щоб перевірити, що значення дійсно збереглося -- а не просто утримується в пам'яті: ```python @pytest.mark.django_db def test_task_default_completed_is_false(): from myapp.models import Task 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_marked_complete(): from myapp.models import Task task = Task.objects.create(title='Finish report', completed=False) task.completed = True task.save() task.refresh_from_db() assert task.completed is True ``` `refresh_from_db()` перезавантажує всі поля з бази даних на існуючому екземплярі на місці -- трохи чистіше, ніж `Task.objects.get(pk=task.pk)`, коли об'єкт вже є. ## Автентифіковані в'юхи через `force_login` `client.force_login(user)` оминає перевірку пароля і безпосередньо позначає сесію як автентифіковану. Надавайте перевагу цьому над `client.login()` у тестах, оскільки це не прив'язує тест до хешування паролів або конфігурації бекенду автентифікації: ```python @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') from myapp.models import Task 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" ``` ## Перевизначення налаштувань через фікстуру `settings` ```python @pytest.mark.django_db def test_welcome_email_is_sent(settings, django_user_model): settings.EMAIL_BACKEND = 'django.core.mail.backends.locmem.EmailBackend' from django.core import mail # припускаємо, що сигнал створення користувача надсилає вітальний email django_user_model.objects.create_user(username='alice', password='x') assert len(mail.outbox) == 1 assert 'Welcome' in mail.outbox[0].subject ``` Фікстура `settings` скасовує кожен встановлений вами атрибут незалежно від того, чи пройшов тест чи провалився. Тому їй завжди надається перевага над патчінгом `django.conf.settings` через `unittest.mock.patch`. Для тестування email зокрема, фікстура `mailoutbox`, описана в MAT1, є чистішою альтернативою -- вона автоматично налаштовує locmem-бекенд і очищає поштову скриньку між тестами без будь-якого ручного налаштування `settings.EMAIL_BACKEND`. ## Тестування з `rf` (RequestFactory) `rf` будує об'єкти `HttpRequest` без проходження URL-маршрутизації або проміжного ПЗ. Тест викликає в'юх-функцію безпосередньо: ```python from myapp.views import task_list @pytest.mark.django_db def test_task_list_with_rf(rf): from myapp.models import Task Task.objects.create(title='RF task') request = rf.get('/tasks/') response = task_list(request) assert response.status_code == 200 ``` Оскільки `rf` оминає URL-маршрутизацію та все проміжне ПЗ запитів -- включаючи проміжне ПЗ автентифікації та сесій -- `request.user` не заповнюється із сесії і за замовчуванням є `AnonymousUser`. **Декоратори в'юх на кшталт `@login_required` все одно запускаються** як частина виклику в'юхи: з анонімним користувачем декоратор перенаправляє. Встановіть `request.user` на автентифікований об'єкт користувача перед викликом будь-якої в'юхи, що перевіряє його: ```python @pytest.mark.django_db def test_my_tasks_with_rf(rf, django_user_model): from myapp.views import my_tasks from myapp.models import Task 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 ``` Компроміс: тести з `rf` швидші і тестують лише логіку в'юхи. Тести з `client` повільніші, але перевіряють, що маршрутизація, проміжне ПЗ та автентифікація працюють разом. Використовуйте `rf` для юніт-тестування в'юх-функцій; `client` -- для інтеграційного тестування повного циклу запиту. ## Тестування Django REST Framework API з `APIClient` Якщо ваш проект використовує DRF, використовуйте `APIClient` з `rest_framework.test` замість Django `TestClient`. `APIClient` розуміє узгодження вмісту DRF, `response.data` та шари автентифікації: ```python import pytest from rest_framework.test import APIClient @pytest.fixture def api_client(): return APIClient() @pytest.mark.django_db def test_task_list_returns_empty(api_client): response = api_client.get('/api/tasks/') assert response.status_code == 200 assert response.data['results'] == [] # .data вже розібрано -- .json() не потрібен ``` `response.data` -- це розібраний Python-об'єкт, вироблений серіалізатором DRF -- словник, список або `OrderedDict`. Це еквівалентно виклику `response.json()`, але відображає вихід серіалізатора DRF безпосередньо, включаючи вкладені зв'язки. Для автентифікації використовуйте `force_authenticate()` замість `force_login()`: ```python @pytest.mark.django_db def test_create_task_as_alice(api_client, django_user_model): user = django_user_model.objects.create_user(username='alice', password='x') api_client.force_authenticate(user=user) response = api_client.post('/api/tasks/', {'title': 'Deploy fix'}, format='json') assert response.status_code == 201 assert response.data['title'] == 'Deploy fix' ``` `force_authenticate(user=user)` встановлює користувача безпосередньо на рівні дозволів DRF -- без сесії, без пошуку токена, без перевірки пароля. Використовуйте для тестування логіки в'юх і серіалізатора ізольовано від питань автентифікації. `force_login()` (для Django `TestClient`) працює на рівні сесії і є правильним вибором, коли вашому тесту конкретно потрібен сесійний куки. Для DRF API, захищених токеном або JWT, `force_authenticate()` завжди є простішим і швидшим.

Довідник: pytest-django

#
## Налаштування pytest.ini ```ini [pytest] DJANGO_SETTINGS_MODULE = myproject.settings testpaths = tests addopts = -v --tb=short ``` ## Вбудовані фікстури | Фікстура | Тип | Призначення | |---|---|---| | `client` | `TestClient` | Повний стек HTTP -- маршрутизація, проміжне ПЗ, в'юхи | | `admin_client` | `TestClient` | Попередньо автентифікований як Django-суперкористувач | | `rf` | `RequestFactory` | Лише об'єкти запитів -- без маршрутизації, без проміжного ПЗ | | `db` | фікстура | Надати доступ до БД тілу фікстури | | `settings` | `UserSettingsHolder` | Перевизначити налаштування Django; автоматично скасовуються після тесту | | `django_user_model` | клас моделі | Враховує `AUTH_USER_MODEL`; працює з кастомними користувачами | | `mailoutbox` | список | Перехоплює вихідні email; автоматично налаштовує locmem-бекенд для тесту | | `live_server` | LiveServer | Запускає реальний WSGI-сервер; доступ до URL через `live_server.url`; для Selenium/Playwright | | `django_db_setup` | session-фікстура | Керує створенням/знищенням БД на рівні сеансу | ## Параметри `@pytest.mark.django_db` | Параметр | За замовчуванням | Примітки | |---|---|---| | `transaction=False` | ✓ | Огортає тест у відкочувану транзакцію (швидко) | | `transaction=True` | | Очищує БД після тесту; потрібен для `on_commit`, `select_for_update` | | `reset_sequences=True` | | Скидає auto-increment ID; лише з `transaction=True` | | `databases` | `['default']` | Список псевдонімів БД, до яких тест може звертатися | ## Шаблони автентифікації ```python # Надається перевага в тестах -- оминає перевірку пароля client.force_login(user) # Також працює -- перевіряє облікові дані через бекенд автентифікації client.login(username='alice', password='secret') # Для rf: встановити request.user вручну (немає проміжного ПЗ для його встановлення) request.user = user ``` ## Шаблони доступу до БД у фікстурах ```python # Варіант A -- маркер на рівні тесту (фікстури успадковують доступ) @pytest.mark.django_db def test_something(): MyModel.objects.create(...) # Варіант B -- фікстура явно запитує db @pytest.fixture def my_obj(db): return MyModel.objects.create(...) # Варіант C -- transactional_db для тестів з transaction=True @pytest.fixture def my_obj(transactional_db): return MyModel.objects.create(...) ``` ## Поширені шаблони ```python # Перевірка збереження (а не лише стан у пам'яті) obj.refresh_from_db() Task.objects.get(pk=task.pk) # POST з тілом JSON client.post('/api/', data={'key': 'val'}, content_type='application/json') # Доступ до даних відповіді (Django TestClient) data = response.json() assert data['key'] == 'value' # DRF APIClient: використовувати response.data (розібрано серіалізатором, .json() не потрібен) # assert response.data['key'] == 'value' # Перевірка місця редиректу assert response.status_code == 302 assert response['Location'] == '/login/' ```
01

Тест: Django-в'юха повертає HTTP 200

#

У вас є Django-в'юха `task_list` за URL `/tasks/`, що повертає JSON-відповідь з ключем `results`, що містить список задач. Налаштуйте `pytest.ini` з `DJANGO_SETTINGS_MODULE` і напишіть тест з вбудованою фікстурою `client`, що: 1. Викликає `GET /tasks/` 2. Перевіряє, що код статусу 200 3. Перевіряє, що JSON відповіді містить ключ `results` 4. Перевіряє, що `results` є списком

# 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):
    # викличте GET /tasks/ і перевірте статус + структуру
    pass
Рішення
# 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)
02

Тест збереження моделі та стійкості в БД

#

У вас є модель `Task` з полями `title` (CharField) і `completed` (BooleanField, default=False). Напишіть два тести: 1. **test_default_completed_is_false** -- створіть `Task`, потім отримайте її з бази даних за первинним ключем і перевірте, що `completed` є `False`. 2. **test_task_can_be_completed** -- створіть `Task` з `completed=False`, встановіть `completed=True`, викличте `.save()`, потім `.refresh_from_db()` і перевірте, що значення збереглося.

import pytest
from myapp.models import Task


@pytest.mark.django_db
def test_default_completed_is_false():
    # створіть і отримайте свіжий об'єкт з БД
    pass


@pytest.mark.django_db
def test_task_can_be_completed():
    # створіть, оновіть, збережіть, оновіть з БД, перевірте
    pass
Рішення
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
03

Тест автентифікованої в'юхи через force_login

#

У вас є в'юха `my_tasks` за `/my-tasks/`, декорована `@login_required`. Вона повертає лише задачі, що належать поточному автентифікованому користувачу, у вигляді JSON `{'results': [...]}`. Напишіть два тести: 1. **test_my_tasks_redirects_anonymous** -- перевірте, що неавтентифікований GET повертає 302. 2. **test_my_tasks_returns_only_owners_tasks** -- створіть двох користувачів і по одній задачі на кожного. Увійдіть як `user1` через `client.force_login()`. Перевірте статус 200 і що відповідь містить рівно одну задачу, що належить `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
Рішення
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"
04

Перевизначення налаштування Django для одного тесту через фікстуру settings

#

У вас є утилітарна функція: ```python # myapp/utils.py from django.conf import settings def get_max_tasks(): return getattr(settings, 'MAX_TASKS_PER_USER', 10) ``` Напишіть два тести: 1. **test_get_max_tasks_with_override** -- використайте фікстуру `settings` для встановлення `MAX_TASKS_PER_USER = 3` і перевірте, що `get_max_tasks()` повертає `3`. 2. **test_get_max_tasks_default** -- не використовуйте фікстуру `settings`; перевірте, що `get_max_tasks()` повертає дефолтне `10`. Обидва тести мають проходити незалежно від порядку виконання.

# 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
Рішення
# 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
05

Тест в'юх-функції безпосередньо через RequestFactory

#

У вас є в'юх-функція `task_list` (не URL -- сама функція). Напишіть тест з фікстурою `rf` (RequestFactory), що: 1. Створює два об'єкти `Task` у базі даних. 2. Будує GET-запит через `rf.get('/tasks/')`. 3. Викликає в'юх-функцію безпосередньо: `response = task_list(request)`. 4. Перевіряє статус 200 і що JSON-відповідь містить рівно 2 задачі. Потім напишіть другий тест для автентифікованої в'юхи `my_tasks`. Оскільки `rf` пропускає проміжне ПЗ, `request.user` не встановлюється автоматично -- призначте його вручну.

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
Рішення
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