Most teams have a version of this conversation: "Our test coverage is at 87%, but every deploy is stressful." The number is high, but the confidence is low. What's wrong?

Code coverage measures how many lines were executed during tests. It doesn't measure whether tests verify correct behavior, whether they cover the cases that matter, or whether a refactor that breaks the application will be detected. You can have 100% coverage and a test suite that tests nothing useful.

The testing pyramid revisited

The classic pyramid model, many unit tests, fewer integration tests, even fewer E2E, is still valid as a principle, but it gets misapplied in practice.

The most common mistake: writing hundreds of unit tests that test implementation, not behavior. When code is refactored, all the tests break, even if the external behavior didn't change. This creates strong negative feedback: refactoring = pain. Teams that go through this stop refactoring.

The correct principle, popularized by Kent C. Dodds: the more your tests resemble how the software is used, the more confidence they give.

Testing behavior, not implementation

The practical distinction:

// ❌ Tests implementation: fragile
it('should call setLoading(true) then fetch then setLoading(false)', () => {
  const setLoading = jest.fn()
  // ... tests internal details
})

// ✅ Tests behavior: reliable
it('shows spinner while loading, then displays 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 first test breaks on any implementation refactor. The second survives any refactor that preserves visible behavior.

The cases that matter to cover

Instead of chasing percentages, think in terms of coverage categories that matter:

Happy path

Does the main flow work? Does valid input produce expected output? This is the minimum, necessary but not sufficient.

Edge cases

What happens with empty input? With extreme values? With null where it's not expected? With very long strings? Most real bugs live here.

Error cases

What happens when the external API fails? When the database is unavailable? When the user lacks permission? If you don't test error paths, you don't know what your users will see when something goes wrong.

Business invariants

The rules that can never be violated: balance can't go negative, a user can't access another user's data, an order can't be approved without an item. These tests are executable documentation of business rules.

Integration tests: the ignored sweet spot

For most applications, integration tests are where the ROI on tests is highest. An integration test that exercises the real endpoint, with an in-memory or test database, covers more behavior with less code than dozens of isolated unit tests.

// Integration test with test database: high confidence
describe('POST /orders', () => {
  it('creates 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)
  })
})

That single test validates: authentication works, input validation works, order logic works, inventory update works, response is correct. Five behaviors in one test.

The right metric to measure confidence

Instead of line coverage, measure:

  • How many bugs went to production that the suite didn't detect in the last 3 months? If the answer is high, you have coverage gaps in important behaviors.
  • How much time do you spend "fixing" tests that broke without behavior changing? If it's high, you have fragile tests that cost more than they deliver.
  • Do you deploy confidently or with fingers crossed? The answer says more about test quality than any coverage number.

60% coverage with well-written tests gives more confidence than 90% coverage with tests that test implementation. Optimize for confidence, not for metric.