Unit Test
Method
The smallest kind of test: one function or module in isolation, fed a value, with its result checked automatically against an expected one.
Why it exists
Checking a change used to be a manual job. Load the screen, log in, click through to the feature, type a value in. One check takes two minutes, so covering ten cases takes twenty, and twenty minutes is not something you spend several times a day. Most of the time, two or three cases got clicked and the rest were assumed.
The incidents came from the rest. Theo changed the discount calculation and checked three amounts on screen — roughly $8, $40 and $75. All fine. But when the discount brought the charge down to zero, payment failed, and the team learned about it from a customer who had redeemed a free coupon. Producing a zero-value order through the interface was awkward enough that it never made the checklist.
Manual checking carried two limits at once. It costs time, so not every case gets covered, and it runs through the interface, so awkward cases get dropped entirely. Pulling the calculating function out and feeding it values directly solves both. Zero, negative numbers and fractions need no screen, and twenty cases finish in under a second. The awkward case stops being the one that gets skipped and becomes one more line in a file.
How to run it
Start by choosing the smallest piece you can pull out. Five steps.
- Pick the unit. A function that takes values and returns a value is the easiest case. The less it touches screens or databases, the easier it is to isolate, which is why pricing calculations and permission checks tend to go first.
- Write it in three parts: arrange, act, assert. Build the input, call the function once, compare what comes back against the expected value. Check one thing per test. Bundle several checks together and a red result sends you hunting for which condition broke.
- List the boundary values. A test built around one ordinary value passes too easily. For an amount, write down zero, negative, fractional, very large, and empty, and make one case for each. Incidents usually live somewhere in that list. The zero-charge failure was a boundary value nobody had written down, not a mistake in the arithmetic.
- Cut the dependencies on the outside world. A mock is a stand-in that only imitates the real thing — instead of calling a card processor, you run against a fake that always answers success. Anything that changes run to run, like the current time or a random value, gets pinned down too, so the same test gives the same result whenever it runs.
- Wire it to run automatically, on save and before a change gets merged. One case finishes in about a millisecond, so 2,000 of them take under a minute all together.
There is also the order where the test gets written before the implementation. That is test-driven development, and its value is that you decide what finished means before building anything.
When it fits and when it doesn't
The value is highest where the rules are complicated. Per-plan price calculations, permission checks, date and duration handling — anywhere the conditions branch several ways. It also works well as a safety net before restructuring, and as a way to leave one reproducing case behind every time a bug gets fixed.
Some things it cannot reach. How elements are laid out on a screen, whether several pieces work once wired together, whether a real payment gets authorized by a real card processor — those belong to integration tests and end-to-end tests instead.
Three limits.
- Everything can pass individually and still break when connected. A calculating function and a storage module are each fine while the units of the value they exchange disagree — one works in cents, the other in dollars, and neither test notices.
- The number invites false comfort. Test coverage is the share of code the tests executed, and even at 80 percent, some of those lines were merely run past with nothing checked.
- Heavy mocking drifts away from reality. Build a fake card processor that always answers success and code unable to handle a real decline response passes every test you have.
There is an upkeep cost on top of that. A test that also checks what a function calls internally, and in what order, breaks on work that changed only the structure while the result stayed identical. What belongs in the assertion is the returned value, not the implementation.
How it sounds at work
"This calculation can be covered with unit tests. We don't need to go up to the screen."
The code is easy to isolate, so verification is fast and cheap.
"Zero and negative cases are missing. Fill in the boundary values first."
Only ordinary values are covered, and the range where incidents happen is empty.
"This test is checking the implementation. Let's rewrite it to check the result."
It is bound to internal structure, so every cleanup breaks it.
Pagedeck concentrates its unit tests on pricing and permission checks. After the zero-charge incident, Theo turned that function's boundary values into eleven cases, and the same class of mistake has been caught on save ever since. The full set of 2,300 cases takes about 50 seconds, which is short enough that people still wait for the result rather than starting something else.
Mia checks for a reproducing test before closing a bug. A fix on its own leaves the same failure free to return in six months, so one bug leaving one test behind became a rule.
What people get wrong
The most common mistake is reading a high coverage number as safety. At 90 percent it looks like nine tenths of the code has been verified. But the number counts lines the tests walked past, not whether anything was actually checked. A test that calls a function and never compares the result still raises coverage. Left in place, the belief pushes teams toward the cheapest way to raise the figure, and incidents keep arriving from behind a 90 percent wall.
The second is expecting tests to slow development down. You are writing something besides the code itself, so it feels that way. The slow stretch is roughly the first two weeks; after that, twenty minutes of manual checking becomes one minute, and the same area gets fixed twice less often. Teams holding onto this belief drop tests first whenever a deadline gets close, which is exactly the period when mistakes are most frequent. The hours saved that week come back as an incident the following one.
Easily confused with
| Term | What it checks | How it differs |
|---|---|---|
| Integration test | Whether several pieces work once wired together | It looks at the connections. Slower and vaguer about causes, but it catches what breaks only when parts meet |
Where to go next
- Testing
see why this scope sits at the bottom of the layers
- Integration test
see what the layer above it adds
Related terms
Last updated 2026-09-16