A team with 87% coverage and painful deploys describes a known problem: the number went up and the team's confidence did not follow.
Coverage counts lines executed during tests. It does not tell you whether the test verifies the right behavior, whether it covers the case that breaks the business, or whether a buggy refactor would pass in silence. You can reach 100% with a suite that catches nothing useful. The Google Testing Blog coverage best-practices guide lists the common misreadings of the metric, and Martin Fowler's TestCoverage bliki describes coverage as a map of the code that went untested.
The pyramid stands, under another name
The classic model (many unit, fewer integration, fewer E2E) holds as a principle. In practice teams get it wrong when they write hundreds of unit tests pinned to the implementation. You refactor, the whole suite breaks, and the team learns that refactoring hurts.
Kent C. Dodds summed up the principle I apply: the more your tests resemble the way the software is used, the more confidence they give. He walks through the reasoning in Write tests. Not too many. Mostly integration. The Google Testing Blog post SMURF: Beyond the Test Pyramid proposes categorizing tests by fidelity and speed instead of by layer, and describes how Google measures it.
Behavior ahead of implementation
// pinned to the implementation: breaks on any refactor
it('calls setLoading(true), then fetch, then setLoading(false)', () => {
const setLoading = jest.fn()
// ...
})
// pinned to behavior: survives refactors
it('shows the spinner while loading, then displays the results', async () => {
render(<ProductList />)
expect(screen.getByRole('progressbar')).toBeInTheDocument()
await screen.findByText('Product A')
expect(screen.queryByRole('progressbar')).not.toBeInTheDocument()
expect(screen.getByText('Product A')).toBeInTheDocument()
})
The second test uses the role-based queries from React Testing Library, which ask for what a person sees on the screen. It survives a change of state library, of fetch method, and of cache format.
The cases the suite needs to cover
Happy path
Valid input produces the expected output. That is the starting point; alone, it protects nothing.
Edge cases
Empty input, an extreme value, null where the API promises a number, a ten-thousand-character string. Production bugs live here.
Error paths
The external API down, the database unavailable, the user without permission. Without a test on that path, you discover the error screen next to the customer.
Business invariants
Balance never goes negative. A user never reads another user's data. An order never approves without an item. That test is the business rule written so the CI executes it.
Integration tests return more confidence per line of code
An integration test against a test database walks the whole endpoint and covers more behavior than dozens of isolated unit tests.
describe('POST /orders', () => {
it('creates the order and deducts inventory', async () => {
await db.product.create({ id: 'p1', stock: 5 })
const res = await request(app)
.post('/orders')
.set('Authorization', `Bearer ${userToken}`)
.send({ productId: 'p1', quantity: 2 })
expect(res.status).toBe(201)
expect(res.body.order.status).toBe('confirmed')
const product = await db.product.findUnique({ where: { id: 'p1' } })
expect(product.stock).toBe(3)
})
})
One test, five behaviors: authentication, validation, order rule, inventory deduction, response shape.
Three numbers that measure confidence
Replace the consolidated coverage number with:
- Production bugs in the last three months with the suite green. Each one points at a behavior gap the coverage metric never showed.
- Hours spent fixing tests that broke with no change in behavior. That is the cost of tests pinned to the implementation, paid in hours the product expected features.
- Deploys where someone asked to wait until after the holiday. That number measures team confidence better than any percentage.
A 60% suite covering business behavior protects more than a 90% suite pinned to the implementation. How Much Testing is Enough? on the Google Testing Blog works through that cost-benefit math, and the same source's post on brittle tests lists the API patterns that avoid coupling to the implementation.
Enjoyed this content?
I build web products and AI solutions the right way — solid architecture, maintainable code, and real delivery.
Let's talk