Writing automated tests for complex and distributed systems is hard! 🤯
If you have been involved in creating integration and end-to-end tests for this type of systems, you will likely recognize this sentiment. And with applications and systems becoming ever more distributed – you only have to consider the rise in popularity of microservices-based architectures to see what I mean – this is a challenge that likely isn’t going to go away soon.
The issues with testing these complex and distributed systems are often caused not so much by the class, component or application you want to test itself, but rather from the dependencies that interact with your test object. These issues manifest themselves in two different ways: when you’re trying to isolate your test object from outside influences (e.g., during unit or component testing) and when you’re trying to incorporate the behaviour of dependencies that are outside of your direct control (e.g., during integration or end-to-end testing).
In this article series, we’re going to take a closer look at how in both situations, simulation of dependency behaviour can help resolve some of these issues.
- You’re here → Increasing Test Efficiency with Simulations & Service Virtualization (Chapter 1)
- Isolating Components in Unit Tests with Mockito (Chapter 2) – Coming Soon!
- Getting Started with Service Virtualization using WireMock (Chapter 3) – Coming Soon!
- Creating More Sophisticated Simulations with WireMock (Chapter 4) – Coming Soon!
Isolating your test object from outside influences
In unit and component testing, you’re generally interested in checking whether or not a single piece of code – this can be a method, a class or a small group of classes for which it wouldn’t make sense to test them individually – behaves as intended. However, apart from the most trivial cases, it is unlikely that these units (components) are built and run in isolation. Much more often, they will have to interact with other, potentially complex, units and components: their dependencies.
A common way to deal with the challenge of isolation is the use of mocks. Mocks are dummy implementations of objects (dependencies) for which you can define the behaviour and the output it generates when you write your test. By using mocks instead of ‘real’ dependencies, you have full control over what output from a dependency goes into the unit or component you want to test, allowing you to test the workings of that unit in more isolation.
In the next article in this series, we’ll take a closer look at using mocking frameworks, as well as examples of their most useful features. These will be implemented in Mockito, a popular Java mocking framework, but the principles apply to other mocking frameworks and (object oriented) programming languages just as well.
Simulating dependencies outside of your control – Service virtualization and stubbing
The other category of dependency challenges is when you do want to incorporate the behaviour of certain components in your test, but these components are posing challenges of their own, such as:
- They’re not available on demand, for example because they’re still under development, or access is shared with many different teams (mainframes, anybody?)
- It is hard to set up the right test data in these components
- It is hard to simulate edge and error cases in these components (ever tried to simulate receiving an HTTP 503 Service Unavailable from a service you invoke but do not control?)
- They require access fees (this is a common scenario with SaaS applications)
Using stubbing or service virtualization can be a fruitful approach to mitigate the risks posed above. Both techniques involve creating simulations that mimic the behaviour needed to perform the required integration and end-to-end tests. Please note that the difference between stubbing and service virtualization is not a well-defined one, and you might see both terms used to describe roughly the same approach. In general, service virtualization solutions typically offer a richer feature set and allow for more dynamic behaviour simulation than stubbing, but their end goal is the same.
The three main benefits of using simulations in your integration and end-to-end testing are:
- The ability to test earlier: If you have a simulation in place that follows a design or contract that’s agreed on beforehand, you don’t need to wait anymore until the other team has delivered a version of the dependency that you can leverage for your testing activities.
- The ability to test more: When the behaviour of the dependency is under your full control, it’s much easier to simulate edge cases and error situations (negative testing) compared to dealing with actual dependencies.
- The ability to test more often: Well-designed simulations are available and ready for testing at the press of a button, allowing you to do integration and end-to-end testing virtually on demand. No more waiting for that time slot when you finally have access to that shared dependency.
Please keep in mind, though, that using stubbing and service virtualization is not a 1-on-1 replacement of testing using ‘the real thing’. It can, however, significantly speed up (‘shift left‘) a lot of testing activities.
In articles three and four of this series, we’re going to take a closer look at WireMock, a popular and open source Java library for stubbing / service virtualization. Using working examples, you’ll see how WireMock (and similar tools) can be used to create realistic simulations that help you to test earlier, more and more often.
If you want to read a more in-depth introduction into service virtualization, you can download and read a free ebook I wrote a couple of years ago on this subject here! 📖😉