Unit Tests
3 minute read
Definition
Verification of the smallest testable piece of code—typically a single function, method, or class—in complete isolation from the rest of the application, network, file system, or external services. Test doubles are used where needed.
Scope & Boundaries
Execution runs entirely in-memory. All external dependencies (databases, APIs, message brokers, system clocks) are replaced with test doubles (stubs, mocks, or fakes).
Characteristics
Millisecond execution speeds, highly deterministic (zero flakiness), and pinpoint failure localization.
Good Practices:
- Test public behavior, not implementation details: Assert on return values and visible side effects rather than internal private state or execution paths.
- Strict isolation: Keep all tests in-memory; mock or stub out network, disk I/O, database, and system time to ensure sub-millisecond execution.
- Single assertion concept: Each test should verify one specific behavior or edge case to maintain pinpoint failure localization.
Anti-Patterns
- Over-mocking: Mocking domain entities, data transfer objects, or language primitives instead of purely external/infrastructure boundaries.
- Testing private methods: Forcing visibility or coupling tests to internal helper methods, which causes refactoring resistance without increasing behavioral confidence.
- Inter-test dependencies: Letting execution order matter or sharing mutable global state between test cases.
Solitary vs. sociable unit tests
A solitary unit test replaces all collaborators with test doubles. A sociable unit test allows real in-process collaborators while still replacing any external I/O. Both styles are unit tests as long as no real external dependency is involved.
When to Run Them
- During development: run the relevant subset of unit tests continuously while writing code. TDD (Red-Green-Refactor) is the most effective workflow.
- On every commit: use pre-commit hooks or watch-mode test runners so broken tests never reach the remote repository.
- In CI: execute the full unit test suite on every pull request and on the trunk after merge to verify nothing was missed locally.
Unit tests are the right choice when the behavior under test can be exercised without network access, file system access, or database connections. If you need any of those, you likely need a component test or an end-to-end test instead.
Examples
A Java sociable unit test exercising real domain logic through its public interface. The collaborators (the pricing policy and the order model) are real objects, not mocks, and the test asserts on the observable outcome - the computed total - rather than on which methods were called:
Connection to CD Pipeline
Unit tests run in the earliest stages of the CD pipeline and provide the fastest feedback loop:
- Local development: watch mode reruns tests on every save.
- Pre-commit: hooks run the suite before code reaches version control.
- PR verification: CI runs the full suite and blocks merge on failure.
- Integrated change verification: CI reruns tests on the merged HEAD to catch integration issues.
They should always halt the CD pipeline on failure.