Django Testing & Debugging
Downloaded from PrepIQ (https://prepiq.online)
Q1. What is Django's TestCase and how is it different from Python's unittest.TestCase?
Django's django.test.TestCase extends Python's standard unittest.TestCase and adds Django-specific features, most importantly wrapping every test method in a database transaction that is rolled back at the end of the test, so tests never leave residual data behind and can run in any order without interfering with each other.
It also provides a test client for simulating HTTP requests, assertion helpers like assertRedirects() and assertContains(), and integrates with Django's fixture loading. For tests that don't need a database at all, SimpleTestCase is a faster alternative that disables the transaction wrapping.
from django.test import TestCase
from .models import Product
class ProductModelTest(TestCase):
def test_str_representation(self):
product = Product.objects.create(name="Laptop", price=999)
self.assertEqual(str(product), "Laptop")
Q2. How does Django's test client work for testing views?
Django's test Client simulates a browser making HTTP requests to your application without needing a running server, calling views directly through Django's URL routing and middleware stack, then returning a response object you can assert against — status code, content, headers, redirect chain, and rendered context.
Because it runs in-process, it's fast and doesn't require sockets or a live server, making it the standard way to test views, forms, authentication flows, and permission checks end-to-end at the HTTP layer rather than calling view functions directly.
from django.test import TestCase
from django.urls import reverse
class ProductViewTest(TestCase):
def test_product_list_returns_200(self):
response = self.client.get(reverse('product-list'))
self.assertEqual(response.status_code, 200)
self.assertContains(response, "Products")
self.assertTemplateUsed(response, "products/list.html")
Q3. What are Django fixtures and when should you avoid using them?
Fixtures are serialized snapshots of database data (JSON, YAML, or XML) that can be loaded into the test database before a test runs, using fixtures = ['products.json'] on a TestCase or the loaddata management command, useful for seeding a known baseline dataset quickly.
Fixtures are best avoided for complex or evolving models: they're brittle to schema changes (a new required field breaks every fixture file silently), hard to read and review in a diff, and don't express test intent clearly. The more maintainable alternative is factory libraries like factory_boy, which build model instances in Python code with sensible defaults and only the fields relevant to each test explicitly overridden.
# Fixture-based (harder to maintain)
class ProductTest(TestCase):
fixtures = ['products.json']
# factory_boy alternative (recommended for evolving models)
class ProductFactory(factory.django.DjangoModelFactory):
class Meta:
model = Product
name = factory.Sequence(lambda n: f"Product {n}")
price = 100
Q4. How do you mock external API calls in Django tests?
Tests that call a real external API during a test run are slow, flaky (network issues, rate limits), and non-deterministic, so external calls should be replaced with mocks that return controlled, predictable responses. Python's built-in unittest.mock.patch replaces the function or method making the external call for the duration of the test.
For libraries like requests, responses or requests-mock are commonly used to intercept and stub HTTP calls at the transport level, verifying both that the correct request was made and that the code correctly handles the mocked response, including error cases like timeouts or 500 responses that would be hard to trigger against a real API.
from unittest.mock import patch
class PaymentServiceTest(TestCase):
@patch('payments.services.requests.post')
def test_charge_success(self, mock_post):
mock_post.return_value.status_code = 200
mock_post.return_value.json.return_value = {"status": "success"}
result = PaymentService().charge(amount=100)
self.assertTrue(result.success)
Q5. What is pytest-django and how does it compare to Django's built-in test runner?
Django's built-in test runner, based on unittest, requires tests as class methods inheriting from TestCase, with setup/teardown handled through class methods like setUp(). pytest-django is a plugin that lets you write Django tests using plain pytest functions and fixtures instead, which many teams find more concise and flexible.
Key advantages include pytest's powerful fixture system for reusable setup with explicit dependencies, parametrized tests (@pytest.mark.parametrize) for running the same test logic across many inputs without duplicated methods, better assertion introspection (detailed failure diffs without needing assertEqual variants), and access to the broader pytest plugin ecosystem (coverage, parallelization with pytest-xdist, etc.).
import pytest
@pytest.mark.django_db
def test_product_creation():
product = Product.objects.create(name="Phone", price=500)
assert product.name == "Phone"
@pytest.mark.parametrize("price,expected", [(0, False), (100, True)])
def test_is_valid_price(price, expected):
assert Product(price=price).is_valid_price() == expected
Q6. How do you test Django REST Framework APIs (APIClient, status codes, serializer validation)?
DRF provides rest_framework.test.APIClient, a subclass of Django's test client tailored for APIs — it handles JSON request/response bodies naturally, supports authentication helpers like force_authenticate() to bypass real login for a test, and returns a response whose .data attribute gives the parsed response content directly.
A thorough DRF test suite checks status codes for each scenario (200/201 for success, 400 for validation errors, 401/403 for auth failures, 404 for missing resources), verifies the exact shape of the returned JSON, and separately unit-tests serializers by instantiating them directly with sample data to confirm validation rules work in isolation from any view or HTTP layer.
from rest_framework.test import APITestCase
from rest_framework import status
class ProductAPITest(APITestCase):
def test_create_product_invalid_price(self):
response = self.client.post('/api/products/', {'name': 'Book', 'price': -10})
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
self.assertIn('price', response.data)
Q7. What is Django Debug Toolbar and what does it help you diagnose?
Django Debug Toolbar is a development-only panel injected into rendered pages that surfaces detailed diagnostic information about the request that produced the page — every SQL query executed (with duplicate/similar query detection), time spent in each phase of the request, template rendering context, cache operations, signal dispatches, and request/response headers.
It's most commonly used to catch N+1 query problems (a query panel showing dozens of near-identical queries is the classic symptom), identify slow middleware or views, and inspect the exact context passed to a template — all without needing to add temporary print statements or a debugger. It should never be enabled in production, both for security (it exposes internals) and performance (the extra instrumentation adds overhead).
# settings.py (development only)
INSTALLED_APPS += ['debug_toolbar']
MIDDLEWARE += ['debug_toolbar.middleware.DebugToolbarMiddleware']
INTERNAL_IPS = ['127.0.0.1']
Q8. How do you use Python's pdb / breakpoint() to debug a Django view?
Inserting Python's built-in breakpoint() (available since Python 3.7, invoking pdb by default) directly inside a view function pauses execution at that exact line when the request hits it, dropping into an interactive debugger in the terminal running the dev server, where you can inspect variables, step through code line by line, and evaluate expressions in the current scope.
This is especially useful for bugs that only reproduce with a real request (session state, middleware-modified request objects, complex queryset chains) where print-statement debugging is slow and imprecise. It only works with Django's synchronous development server (runserver), not in a production WSGI/ASGI deployment, and must be removed before committing since it would hang any request that reaches it.
def product_detail(request, pk):
product = Product.objects.get(pk=pk)
breakpoint() # execution pauses here; inspect `product`, `request`, etc.
return render(request, 'product_detail.html', {'product': product})
Q9. How do you measure and improve test coverage in a Django project?
The coverage.py library, run alongside Django's test suite (coverage run manage.py test), tracks which lines of source code were actually executed during the test run, and coverage report/coverage html produce a summary showing the percentage of covered lines per file, highlighting exactly which lines were never exercised by any test.
High coverage doesn't guarantee correctness — a test can execute a line without actually asserting anything meaningful about its behavior — so coverage is best treated as a tool for finding untested code paths (especially error-handling branches and edge cases that are easy to forget) rather than a target number to chase for its own sake. CI pipelines commonly enforce a minimum coverage threshold to prevent untested code from being merged.
# Run tests with coverage tracking
coverage run --source='.' manage.py test
coverage report -m
coverage html # generates browsable HTML report
Q10. What is the difference between Django's assertQuerysetEqual, assertNumQueries, and how do you test for query efficiency?
assertQuerysetEqual compares the contents of a queryset against an expected list of values, useful for verifying a view or method returns the correct set of objects in the correct order. assertNumQueries is a context manager that asserts exactly how many SQL queries were executed within its block, making it the standard tool for catching N+1 query regressions in tests before they reach production.
Wrapping a view call in assertNumQueries(N) fails the test if the view suddenly executes more queries than expected — for example, after someone removes a select_related() call — turning a performance regression into a hard test failure instead of a silent slowdown discovered later in production monitoring.
def test_product_list_query_count(self):
Product.objects.bulk_create([Product(name=f"P{i}") for i in range(10)])
with self.assertNumQueries(2): # 1 for products, 1 for related category prefetch
response = self.client.get(reverse('product-list'))
self.assertEqual(response.status_code, 200)
Django Testing & Debugging
What is Django's TestCase and how is it different from Python's unittest.TestCase?
Django's django.test.TestCase extends Python's standard unittest.TestCase and adds Django-specific features, m...
How does Django's test client work for testing views?
Django's test Client simulates a browser making HTTP requests to your application without needing a running se...
What are Django fixtures and when should you avoid using them?
Fixtures are serialized snapshots of database data (JSON, YAML, or XML) that can be loaded into the test datab...
How do you mock external API calls in Django tests?
Tests that call a real external API during a test run are slow, flaky (network issues, rate limits), and non-d...
What is pytest-django and how does it compare to Django's built-in test runner?
Django's built-in test runner, based on unittest, requires tests as class methods inheriting from TestCase, wi...
How do you test Django REST Framework APIs (APIClient, status codes, serializer validation)?
DRF provides rest_framework.test.APIClient, a subclass of Django's test client tailored for APIs — it handles...
What is Django Debug Toolbar and what does it help you diagnose?
Django Debug Toolbar is a development-only panel injected into rendered pages that surfaces detailed diagnosti...
How do you use Python's pdb / breakpoint() to debug a Django view?
Inserting Python's built-in breakpoint() (available since Python 3.7, invoking pdb by default) directly inside...
How do you measure and improve test coverage in a Django project?
The coverage.py library, run alongside Django's test suite (coverage run manage.py test), tracks which lines o...
What is the difference between Django's assertQuerysetEqual, assertNumQueries, and how do you test for query efficiency?
assertQuerysetEqual compares the contents of a queryset against an expected list of values, useful for verifyi...