I got this e-mail from a reader today:

I don’t know how can I test asynchronous systems. We develop Voice Communication System applications and everything is based on (asynchronous) request-response. I’m trying to write unit tests for such application […] What is the preferable way to do it? Here I think I must test the whole system chain…

Asynchronous systems seem to be one of the most problematic areas for automated testing. The issue with this is that often too many things get tested at the same time. Think about whether you testing the business logic of the process, technical infrastructure integration or the fact that all the components can talk to each-other.

Most asynchronous systems I’ve seen have some sort of pre-validation of the request, then asynchronous dispatching, then processing and publishing the reply (either saving it to the database or pushing on a message queue, for example). Each of these steps might have some business logic which should be tested. My advice is to:

  • clearly separate the business logic from infrastructure code (eg pushing to a queue, writing to the database).
  • specify and test the business logic in each of the steps in isolation using test doubles. This makes it much easier to write and execute tests, as these tests can be synchronous, don’t need to talk to the real database etc. these tests give you confidence that you have the right business logic.
  • implement technical integration tests for the infrastructure code (queue or database implementations of repositories). these tests can be fairly simple as they don’t have complicated business logic in them. these tests give you confidence that you are using the infrastructure correctly.
  • have one end-to-end integration test that verifies that all components talk to each other correctly. this can execute a simple business scenario that touches all the components and block on a queue waiting for response or poll the database to check for the result. This gives you confidence that the configuration works.

Moving down from business over infrastructure to end-to-end tests, I would normally expect to see the number of tests decrease significantly and time to execute individual tests increase significantly. By executing complicated business logic tests against in-memory synchronous structures you will get the benefit of faster test feedback and better stability.

What you definitely should not be doing is running functional tests throughout the whole pipeline. Such tests will be very complicated and brittle.