Testability is the ease of isolating application logic from effects and evaluating it with automated or systematic tests. A testable architecture separates interfaces from implementations, allowing real dependencies to be replaced with mocks or dummy data. section 15.2.1 section 15.2.5
Dependency inversion is the central design move. Functional interfaces, object-oriented interfaces, free monads, service handles, and first-class functions can all provide replaceable implementations. Without that boundary, naked effects make behavior difficult to isolate and test. section 15.2.1 section 15.2.5
Testability improves when an architecture provides:
- Interfaces that permit mock implementations and controlled dummy responses. section 15.2.1
- Isolation between business logic and the actual environment, supporting white-box unit tests. section 15.2.1
- Restrictions that prevent implementation code from bypassing the intended effect boundary. section 15.2.5
- Low coupling and high cohesion, which make mocking practical for IO-bound applications. section 15.3
An innocent function and a dangerous shortcut
A labyrinth generator can obtain random dimensions through an abstract language interface and remain replaceable in tests. A similar IO function can secretly execute an unrelated destructive command before generating the labyrinth; that hidden effect undermines testability. The contrast shows why effect restrictions matter, not merely whether code has a visible interface. section 15.2.1
Table 15.1 Comparison of the testability of different approaches (view table figure)
| Dependency injection option |
Effect restrictions |
Bypassing restrictions |
Testability in general |
|
|---|---|---|---|---|
| Bare IO |
No DI |
No |
Easy |
Very low |
| ReaderT over IO |
Value based |
No |
Easy |
Low |
| Service handle within IO |
Value based |
No |
Easy |
Low |
| Final tagless/mtl |
Type based |
No |
Possible |
Low |
| Free monads |
Value based |
Yes |
Impossible |
Best |
Compare dependency injection, effect restrictions, bypassing restrictions, and overall testability across bare IO, ReaderT, service handles, final tagless/mtl, and free monads. section 15.2.5


