Status Quo

There is significant artistic variance on how to craft tests which provide developers quick and useful feedback. A common point of contention is the number of files which should be included within these quick and useful tests. The most popular strategy is to have a single file under test at a time.

Most popular solitary unit testing

The solitary unit testing strategy is popular for a reason. They are guaranteed to run fast and are typically simple to author.

Motivation

Solitary unit tests while popular, have a few drawbacks. First and foremost it hinders our ability to confidently refactor across multiple files. An individual file working correctly doesn’t imply it works well with its neighbors.

Refactoring multiple files

A second drawback to solitary unit tests is the need to update tests when refactoring. This effectively doubles the amount of code change during a refactor even when the observable behavior of the system hasn’t changed.

Solitary refactor damage multiplier

This lack of confidence and double-work of refactoring is discouraging to those who seek to improve and refine the structure of the code base.

Solution

Proposed solve to unlock refactoring freedom is to prioritize sociable unit tests which focus on layer interfaces. In doing so we get higher confidence when refactoring with zero test change overhead.

Refactoring freedom

These tests will also get to piggy-back on layer isolation making them more stable and isolated from changes in other parts of the code base.

Layer change isolation

Risks and mitigations

The proposal to prefer unit testing through a layer entry point does not come without its own fair share of drawbacks. These are acknowledged with mitigations listed below.

Risk Mitigation
Unit test takes longer to run
  • Restrict scanning to the layer under test
Test setup becomes more complex
  • Maintain reusable layer-specific testing configurations
  • Maintain convention for factory methods to default injected dependencies to the happy path
  • Prefer SAM interfaces to simplify mocking dependencies
Difficult to test exhaustively
  • Allow fallback to solitary tests based on developer judgement