You should probably add Growing Object Oriented Software, Guided by Tests (Freeman and Pryce, 2009) to your bookshelf
Does anyone have advice on a good approach for this problem?
Basic idea: you want to choose a design such that the effects can be decoupled from your complicated logic. More specifically, you do that in such a way that the test can control the effects observed by your complicated logic.
That gives you a few benefits right away
- Because the test subject is no longer coupled to the real effects, the observed behaviors are more stable
- Because the tests controls the effects, it becomes a lot easier to measure the test subject in conditions that would be difficult to reproduce in a live setting.
So the effect-doers become arguments to your test subject; in your production code, the composition root wires the subject up to the "real" implementations; but in your test code, you instead provide implementations that are specialized for testing (fast, deterministic, measurable, etc).
Another approach (less common, from what I can see) is to turn this idea around - you create a little state machine that can interpret the responses from effects and announce what effects should be done next, and pass instances of that state machine to something that knows how to do all of the effects.
State machines are easy to test: you pass data structures in, you get data structures out, and there are no unstable effects to worry about .
Recommended Viewing