Learning How to Write Tests with pytest in Late 2024

Anne
Django Unleashed
Published in
3 min readAug 29, 2024

--

Introduction

I’ve been working with Django for a while now, and I’ve always used Django’s TestCase for testing. But recently, I came across some code in the Sentry repository where they were using pytest.fixture, and it got me curious. So, I decided to dive into learning pytest. I’m still getting the hang of it, but I’m making progress. I’m writing this as a note to myself on how to set it up and how I’m using it.

In this post, I’ll walk through setting up pytest-django using uv to install the necessary packages, but you can also use pip if you prefer.

Setting Up pytest-django in Your Django Project

  1. Install pytest-django and Other Dependencies

If you’re using uv, you can install the required packages with the following command:

uv pip install pytest pytest-django

Alternatively, if you’re using pip, you can install them like this:

pip install pytest pytest-django

2. Create a pytest.ini file

Create a pytest.ini file in the root directory of your project

[pytest]
DJANGO_SETTINGS_MODULE = my_django_project.settings

Replace my_django_project.settings with the actual path to your Django settings file.

Writing Basic Tests

In this example, @pytest.mark.django_db indicates that the test requires database access. Use this marker only when necessary to avoid slowing down your tests.

#tests.py

import pytest
from django.urls import reverse
from rest_framework.test import APIClient

@pytest.mark.django_db
def test_my_view():
client = APIClient()
url = reverse('my_view')
response = client.get(url)
assert response.status_code == 200

Run Test

Run test with the following command uv pytestor pytest

Best Practices for Writing Tests

  1. Use pytest.mark.django_db Only When Needed:

Adding @pytest.mark.django_db to your tests can slow them down because it involves setting up and tearing down the database. Use this marker only when necessary to keep your tests running quickly.

2. Leverage Fixtures for Reusable Test Data:

Fixtures help keep your tests clean and avoid redundancy. Define a fixture once and reuse it across multiple tests.

3. Use client Fixture for DRF API Tests:

Instead of manually creating an API client in every test, use the client fixture provided by pytest-django

4. Parameterize Tests to Cover Multiple Scenarios:

Use pytest.mark.parametrize to run a test with different sets of inputs. It helps you avoid writing multiple similar tests and makes it easy to test various scenarios.

@pytest.mark.parametrize("username, expected_status", [
('validuser', 200),
('invaliduser', 404),
])
def test_user_view(client, username, expected_status):
response = client.get(f'/api/users/{username}/')
assert response.status_code == expected_status

5. Use pytest's Built-In Assertions:

It makes your test code more readable and easier to write.

6. Organize Tests with conftest.py :

Place common fixtures and test configurations in a conftest.py file.

7. Use pytest-xdist for Parallel Testing:

Speed up your test suite by running tests in parallel using the pytest-xdist plugin. Parallel execution can significantly reduce the time it takes to run all your tests, especially in large projects.

pip install pytest-xdist
pytest -n auto

8. Use pytest-cov for Test Coverage:

It helps you understand which parts of your code are tested and which are not, ensuring better coverage.

pip install pytest-cov
pytest --cov=my_django_project

9. Keep Tests Isolated:

Each test should be independent to avoid interdependencies that could lead to unreliable results.

10. Test Edge Cases:

Make sure to test not only the expected behavior but also potential edge cases.

Conclusion

  • Switching to pytest-django has streamlined my testing process, making it easier to write, maintain, and understand my tests.
  • Using uv to manage my packages has also simplified setup and maintenance.

--

--

Anne
Django Unleashed

just some sleepy coder, coding for my cats food.