RizTech Academy logo
RizTech Academy
Testing and QualityLesson 1 of 425 min

What to test in a Django app, and what not to

Before writing a single test, it is worth being clear about what to test, because the most common testing mistakes are not bad assertions — they are testing the wrong things. Interns often either write no tests ("no time") or write dozens of low-value ones that test Django itself rather than their own code. This lesson is the judgement: what is worth testing in a Django application, what is not, and how to spend limited testing effort where it actually protects you.

Why test at all — the honest reason

Tests are not about proving code is perfect. They are about catching a regression before your users do — knowing that a change you just made did not break something elsewhere. On a real project this is the difference between shipping confidently and shipping with your fingers crossed. For a clinic system holding appointments and medical records, "did my change break the billing calculation?" is a question you want answered by a test in CI, not by a patient's complaint. The merging-is-delegated reality of this course makes it sharper: with changes going live, a test suite is the safety net that lets you move fast without breaking things.

Test YOUR logic, not Django's

The single most important principle: test the code you wrote, not the framework. Django's ORM, its form validation, its auth — these are tested exhaustively by Django's own maintainers. Writing a test that a CharField stores a string, or that filter() filters, tests Django, not your application, and adds maintenance for no protection. What is worth testing is your own logic:

  • Business rules — a fee calculation, an eligibility check, a status transition ("an appointment can go booked → done but not cancelled → done"). These are yours, and you can break them.
  • Custom model methods and managers — your Appointment.objects.booked() returns what you intend (verified in the reference tests: it returns exactly the 3 booked appointments).
  • Your validation — your clean_phone rejects a bad number (yours, not Django's built-in required check).
  • Your views' behaviour — the create view redirects on success and re-renders with an error on failure (verified behaviour).
  • Your permissions and access rules — an unauthorised user is blocked; a user sees only their own records. Security logic is exactly what you must test.

The test that earns its keep is one that fails when you introduce a bug in your logic — not one that would only fail if Django itself were broken.

The testing pyramid, applied to Django

A useful shape for where to spend effort:

  • Many fast unit-ish tests of models, managers, business logic, and pure functions — they run in milliseconds and pinpoint exactly what broke. The bulk of your suite.
  • Fewer integration tests of views and the request/response cycle (using the test client) — they cover "the whole thing works together" but are slower and less precise about what failed.
  • Very few end-to-end tests (a browser driving the real UI) — high value for critical flows, but slow and brittle, so reserve them for the handful of journeys that must never break (a patient booking an appointment, say).

Most of your tests should be the fast, focused kind. A suite that is all slow end-to-end tests is painful to run and vague when it fails; a suite of fast, specific tests is run often and tells you precisely what broke.

Prioritise the failure and the risky paths

With limited time, test in this order:

  1. The paths where a bug is expensive — anything touching money (billing, fees), medical data, and permissions. A bug here has real consequences, so these get tested first.
  2. The failure cases — invalid input is rejected, unauthorised access is blocked, a missing object 404s. As the API lesson stressed, failures are where things break and are most often untested.
  3. The core happy paths — the main flows work end to end.
  4. The edge cases you have actually hit — when you fix a bug, write a test that reproduces it, so it never comes back (a "regression test").

Notice what is not at the top: exhaustively testing every getter, every trivial view, every framework feature. Coverage-for-coverage's-sake produces a large, slow suite that tests mostly Django. Aim instead for high-value coverage of your own risky logic.

Django's test tools, briefly

Django gives you what you need out of the box (the next lessons use them):

  • TestCase — the base class; each test runs in a transaction rolled back afterwards, so tests are isolated and do not pollute the database or each other. Verified: the reference model tests run and roll back cleanly.
  • The test Client — makes requests to your views in-process and returns the response to assert on.
  • manage.py test (or pytest with pytest-django) — the runner. Many teams prefer pytest for its concise assertions and fixtures; both are fine, and this course's patterns work with either.

You do not need extra tooling to start — TestCase and the client cover most of it. The skill is not the tools; it is choosing what to test.

Check your work

Why test. To catch regressions before users do — the safety net that lets you change code (and, here, deploy) with confidence.

The core principle. Test your logic, not Django's — business rules, custom methods/managers, your validation, your views' behaviour, your permissions. A test that only fails if Django itself broke is wasted.

The pyramid. Many fast model/logic tests, fewer view/integration tests, very few end-to-end — most of the suite should be fast and specific.

Priority order. Expensive-if-wrong paths (money, medical data, permissions) → failure cases → happy paths → regression tests for bugs you have hit.

What not to do. Chase coverage of trivial getters and framework behaviour — it produces a slow suite that tests Django, not your app.

The tools. TestCase (transaction-isolated), the test Client, and manage.py test or pytest — no extra tooling needed to start.

Practice

  1. List five things in Nidaan worth testing and five not worth testing; for each, say whether it is your logic or Django's.
  2. For your custom manager method, write one test asserting it returns what you intend — and notice it would fail if you broke the manager, not if Django broke.
  3. Categorise ten hypothetical tests into the pyramid layers (unit / view-integration / end-to-end).
  4. Take a business rule (a fee calculation or status transition) and write the failure-case test first, then the happy-path test.
  5. Write a regression test for a bug: introduce a bug in a method, write a test that catches it, then fix the bug and watch the test pass.
  6. Reason about why a test that "a CharField stores text" is not worth writing.

Official documentation

Next: testing models and business logic.

Stuck on this lesson?

Being stuck is part of it — but being stuck alone for three days is not. Our internship programme pairs this curriculum with code review and one-to-one help from working developers, and it is free.

About the internship