Question

How to unit test code that requires multiple side effects?

For example, making an invoice. Simple action requires few thing to happen simultaneously:

  • create invoice in db
  • send invoice to backend
  • print slip
  • open cash drawer (if cash payment) (Simplified)

I am having a hard time designing an object that does this and respects SRP, and therefore is easy to test.

Does anyone have advice on a good approach for this problem?

Was it helpful?

Solution

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

OTHER TIPS

The realm of unit tests is in memory within a single process.

What you have listed are integration points, they cannot themselves be unit tested.

What you can do is push the actual integration code out into a collaborator class, and pull the orchestration/business logic in to a Unit Testable class.

The collaborator isn't unit testable, but it is the smallest amount of code needed to integrate with an external system. Making it much easier to visually verify, and allows an integration test to ignore your business logic.

The side effects would be implemented as events or messages being sent to a queue or injected interfaces being called. The test could instantiate the subject under test and subscribe to its events or provide the interfaces to the queues or the inserted objects. Then you can test if the events are triggered as expected or the expected messages are indeed posted to the queues or the dummies are being called. This is where the unit test ends, you do not want a printer to actually spit out paper.

Licensed under: CC-BY-SA with attribution
scroll top