Unit Testing Best Practices

Unit tests verify individual components work correctly in isolation — a single function, method, or class, with its dependencies controlled. They're the fast, focused base of the testing pyramid: when they fail, they point straight at the broken unit, and they run in milliseconds so you can run thousands on every change.

The difference between a test suite that helps and one that fights you comes down to discipline: each test checks one behavior, has a name that explains intent, isolates the unit from the outside world, and tests the cases that actually break — not just the happy path.

TL;DR

Quick Example

The Arrange-Act-Assert shape, in three clear steps:

Core Concepts

What makes a good unit test (FIRST)

Descriptive naming

One Behavior Per Test

Isolating Dependencies

Replace slow or unpredictable collaborators (HTTP, DB, clock) with test doubles so the test exercises your unit:

See Mocking & Stubbing.

Test Edge Cases

The happy path rarely breaks — the boundaries do:

Common Patterns

Fixtures

Share setup without copy-paste:

Parameterized tests

One test, many cases:

Best Practices

Common Mistakes

Testing implementation instead of behavior

Over-mocking

FAQ

How many assertions should a test have?

Aim for one behavior per test — which often means one logical assertion, though a few assertions checking facets of the same outcome are fine. The goal isn't a literal single assert; it's that a failing test points to exactly one broken behavior. Avoid bundling unrelated checks into one test, since the first failure hides the rest.

What's the difference between a mock and a stub?

A stub provides canned answers to calls (it feeds data in); a mock also verifies that the right calls happened (it checks interactions). Use stubs to control inputs and mocks when the interaction itself is the behavior under test. Over-using interaction mocks couples tests to implementation — see Mocking & Stubbing.

Should I test private methods directly?

Usually no — test private behavior through the public interface. If a private method is complex enough to want its own tests, that's often a signal it should be extracted into its own unit (a separate function/class) with a public API worth testing. Testing privates directly couples tests to implementation and breaks on refactors.

How do I unit-test code that uses the current time, randomness, or the network?

Inject those dependencies rather than reaching for them globally. Pass in a clock, a random source, or an HTTP client so tests can supply deterministic fakes. This is the core of testable design — and the same dependency injection that TDD pushes you toward. See TDD.

Related Topics

References