1

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?

3
  • Dependency Injection, Interface Segregation, Mocks… That is the usual approach. This not far from what is usually covered by Software Architecture courses, et. al. Have a class whose single responsibility is to coordinates other classes with more specific responsibilities. Then inject those. Then define a interface for them, so you can replace them. Then create test mocks against those interfaces. Edit: This might pass as answering in a comment, sorry about that. Yet, I believe this is expected knowledge for many here. did not down vote. Advice: Break down the problem. Ask narrower questions. Commented Jan 21, 2021 at 22:10
  • This illustrates one of the good points of unit testing: If it is difficult to test something that has multiple side effects, it's a strong hint that the code should be refactored into smaller, more testable pieces. It that an option for your code? Commented Jan 21, 2021 at 23:02
  • @Theraot even though I am fairly inexperienced with testing, I do use DI, Interface Segregation, and mock and I do try to build my classes with single responsibility in mind...my problem is when those single-responsibility components need to come together as one unseparated action.. Commented Jan 22, 2021 at 12:24

3 Answers 3

5

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

3
  • Thanks for mentioning of Another approach Commented Jan 22, 2021 at 5:18
  • I approve this message. I would have wrote basically the same thing, but with more words. I'd probably gone about asynchronous code, functional purity, architectural lines, and when to use one approach instead of the other. Commented Jan 22, 2021 at 5:38
  • Thank you for your answer, links and suggested read! Just ordered a copy of Growing Object Oriented Software. Commented Jan 22, 2021 at 12:27
2

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.

1

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.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.