# Testing Django Apps — Django / FastAPI

Source: https://www.skillbyai.com/en/django-fastapi/q-djtest

> TestCase, the test client and query counts.

## Fast, isolated database tests

Django's `TestCase` wraps each test in a transaction that is rolled back, with `setUpTestData` creating shared fixtures once per class. The built-in **test client** performs requests against your URLs without a server, and `assertNumQueries` guards performance. `manage.py test` creates a separate test database automatically. Many teams use pytest with pytest-django for fixtures and nicer output.

## Verified and hardened

Both frameworks make testing straightforward; Django adds strong security defaults and checks.

![Three ideas: testing Django, password security, deployment checks.](assets/figures/django-fastapi/section-7-map.svg) — Figure 7.1 — Tests, passwords and deploy checks.

## The catalog tests

I ran this with Django 6.1.1 and Python 3.12 in a demo project (shopsite with a catalog app) using SQLite. Three tests: filtering by price and stock, a 404 for a missing product, and a query-count guard.

```python
from decimal import Decimal

from django.test import TestCase
from django.urls import reverse

from .models import Category, Product


class ProductApiTests(TestCase):
    @classmethod
    def setUpTestData(cls):
        office = Category.objects.create(name="Office")
        Product.objects.create(category=office, name="Pen", price=Decimal("899"))
        Product.objects.create(category=office, name="Notebook", price=Decimal("120"))
        Product.objects.create(category=office, name="Lamp", price=Decimal("1499"), in_stock=False)

    def test_list_filters_by_price_and_stock(self):
        r = self.client.get(reverse("product-list"), {"max_price": "1000"})
        self.assertEqual(r.status_code, 200)
        self.assertEqual([p["name"] for p in r.json()["results"]], ["Notebook", "Pen"])

    def test_missing_product_is_404(self):
        self.assertEqual(self.client.get(reverse("product-detail", args=[999])).status_code, 404)

    def test_list_uses_two_queries_or_less(self):
        with self.assertNumQueries(1):
            self.client.get(reverse("product-list"))
```

## Running the tests, run

I ran this with Django 6.1.1 and Python 3.12 in a demo project (shopsite with a catalog app) using SQLite. All three tests pass (timing replaced with (time)); the list view needs exactly one query thanks to select_related.

```bash
python manage.py test catalog -v 2
```

Output:

```
test_list_filters_by_price_and_stock (catalog.tests.ProductApiTests.test_list_filters_by_price_and_stock) ... ok
test_list_uses_two_queries_or_less (catalog.tests.ProductApiTests.test_list_uses_two_queries_or_less) ... ok
test_missing_product_is_404 (catalog.tests.ProductApiTests.test_missing_product_is_404) ... ok
Ran 3 tests in (time)
OK
```

**Quiz:** What does Django's TestCase do after each test?

- [ ] Deletes the database file permanently
- [x] Rolls back the database changes
- [ ] Commits changes to production
- [ ] Restarts Python

*Answer:* Rolls back the database changes. Isolated tests.
