Answer
Yes, unit testing is necessary in web development for the rules that decide a result, such as form validation, deadlines and status changes: one unit test per branch and boundary, run in CI on every pull request.
Verdict on the code under testYes
Why
- Blast radiususers
- Change frequencyregularly
- Detectabilityeventually
- Reversibilitywith-effort
- Test costmoderate
Yes, unit tests are necessary in web development for the rules that decide a result. My typical case is a team's customer-facing web application with forms and CI, where a feature adds a rule such as when event registration closes. Blast radius is users and Change frequency is regularly. Detectability is eventually, because a wrong rule renders a plausible page and raises no error, and Reversibility is with-effort, since the damage needs a hotfix and support replies. Test cost is moderate, about an hour per rule, so rule R11 gives Test.
| When | Decision | Why |
|---|---|---|
| The web application computes a cart total, a discount or a shipping fee | Test mandatory: unit test every price rule, its rounding and its boundary values | Blast radius rises to money and Reversibility to costly, because a wrong charge ends in refunds |
| A handler or query decides which account may read a record, such as an organisation ID filter | Test mandatory: for each role, one endpoint test in which a request from another account is denied | Blast radius rises to safety-or-legal and Detectability to never, because a leaked record raises no error |
| The code is a request handler or template that endpoint tests in CI call with a distinct value in each form field | Do not unit test the handler; keep the endpoint tests that call it | Detectability moves to immediately and Reversibility to trivial, because a swapped or missing field fails an endpoint test before the merge |
| A rule changes a few times a year, such as hiding members inactive for 90 days from the team page | Test minimally: one unit test of the main path, plus a regression test for each bug | Change frequency falls to rarely |
| A layout break appears only in a browser that nobody on the team opens, and no end-to-end suite runs there | Test it differently: track the form completion rate per browser and alert when one browser drops | Test cost rises to heavy and Reversibility falls to trivial: a test needs that browser in CI, and a layout break stores nothing |
| The web project is a tool that only you use, such as a personal reading list | Do not test; look at the page after each change | Blast radius falls to none, because only you run the site and bear a failure |
What breaks if you don't test
Web code hides rules in handlers, templates and front-end components, where a browser suite reaches only the path it clicks, at the hour CI runs. When a change breaks a boundary, such as a deadline in the user's time zone, the page still renders and browser tests stay green. Users find the wrong answer days later.
What you lose if you over-test
A unit test of a request handler mocks the request, the ORM and the template engine, then asserts which calls the handler made. It fails when you rename a query method, and it passes when the page shows the wrong field. Coverage still marks each handler as tested.
How to test
- Move each rule out of the handler or component into a function that takes plain values, such as the current time.
- Unit test each rule with one test per branch and boundary, using Vitest, pytest or your framework's runner.
- Test handlers and queries with endpoint tests against a test database, such as the Django test client, with a distinct value in each form field.
- Keep browser tests for sign-up and the main flow, and run all three levels in CI on every pull request.
When the answer changes
- The code charges money or decides who may see a record.
- The site has no rules of its own, only framework forms that endpoint tests call.
- Only you use the site.
Real incident + Code example
The deadline that closed at midnight UTC
On a community events site I worked on, registration was meant to close at midnight before the event, in the event's time zone. The handler compared the current time with midnight UTC. Our browser suite registered for an event a month ahead and passed. For 23 days, events in New York closed registration at 8 p.m. the evening before, and 60 organisers asked support why last-evening sign-ups stopped. The fix was one line; the support replies took two days. Two tests now hold the boundary:
import { expect, it } from "vitest";
import { registrationOpen } from "./registration";
const event = {
startsAt: "2026-10-10T09:00:00-04:00",
timeZone: "America/New_York",
};
it("is open at 23:59 New York time the evening before", () => {
expect(registrationOpen(event, new Date("2026-10-10T03:59:00Z"))).toBe(true);
});
it("closes at midnight New York time, which is 04:00 UTC", () => {
expect(registrationOpen(event, new Date("2026-10-10T04:00:00Z"))).toBe(false);
});
The first test fails on the old code, which closed at 00:00 UTC. A browser test could reach the same boundary only with a fake clock in both the browser and the server.
Related questions
FAQ
- Is it necessary to write unit tests in web development?
Yes, unit tests are necessary in web development for the rules that decide a result, such as validation, deadlines and status changes. Write one test per branch and boundary, and run the tests in CI on every pull request.
- Are end-to-end tests enough for a web application?
No, end-to-end tests are not enough for a web application with rules, because a browser test reaches only the path it clicks at the time it runs. A boundary such as a deadline in another time zone needs a unit test that passes the time as a value.
- Should I unit test controllers in a web application?
No, a controller that reads a form, calls a service and renders a response needs no unit test when an endpoint test in CI calls it with a distinct value in each field. A mock-based controller test asserts calls and fails on refactors. Unit test the rules that the controller calls.