Contents

Engineering Craft › Refactoring

Seam

A place where you can change behavior without editing the code there.

Also known as: seam, seam in testing, enabling point

A seam is a place in the code where you can change behaviour without editing the code at that point. Michael Feathers introduced the term for working with legacy code: before you can test or refactor tangled code, you first create seams — substitution points — so you can swap one behaviour for another (often a test double) and get a foothold.

A seam usually has two parts: a place where a decision is made (an “enabling point”), and the mechanism that lets you change what that decision picks. Common kinds:

  • Object seam — replace a dependency with a test double through dependency injection, a constructor parameter, or a factory.
  • Link/import seam — substitute a different implementation at link or module-load time.
  • Interface seam — swap the concrete implementation behind an interface, which is the dependency-injection form most people mean.
legacy:  order.save(); sendEmail(order);        # can't test without emailing
seam:    order.save(); notifier.send(order);    # inject a fake notifier in tests

That one injected notifier is the seam: production passes the real one, the test passes a fake, and no email is sent. The code path didn’t change; the decision point became substitutable.

The classic mistakes:

  • Trying to test before there’s a seam. If a function reaches out to a database or news up its dependency internally, there’s no substitution point, so tests need the real thing. Add the seam first, then test.
  • Seams everywhere. Every injection point is also added indirection. Add seams where you need to break a dependency; don’t wrap everything “just in case”.
  • Changing behaviour while adding the seam. Introducing a substitutable point should be a behaviour-preserving refactor. Use characterization tests to pin current behaviour before you touch it.
  • Confusing a seam with an interface for its own sake. A seam exists to enable a change — testing, swapping an implementation. If nothing needs to vary, it’s premature abstraction.
  • Seams that leak the real thing. A “fake” that still hits the network isn’t a seam worth having. Make the substitution clean.

Seams are how you make untestable code testable enough to refactor it safely: create the substitution point, write a characterization test that captures the current behaviour, then improve the design behind it. It’s the first move in almost every “add tests to legacy code” playbook.