Service Testing: Must-Have for Microservices
Service testing should be part of every microservices migration strategy
Automated testing for monolithic applications includes unit tests, API integration tests and end-to-end UI tests. The traditional recommended test pyramid has unit tests at the base of the pyramid and UI tests at the top.
In practice, however, the middle API integration test layer often ends up relatively light. The downside of inadequate integration tests is that it leads to over-dependence on UI tests. UI tests tend to be fragile, and test failures tend to be relatively frequent. Due to the lack of adequate API tests, test failure analysis requires extra effort to determine the source of the problem. We have heard from a number of teams that the test failure analysis process must encompass both the UI and the APIs due to lack of adequate API tests, and requires significant time and effort.
With microservices applications, number of API interactions increase significantly as each service interacts with several producer services. In addition, each service comprises of business logic to satisfy the service’s consumers. Therefore, developers must now ensure that their changes to a service haven’t disrupted the following:
Business logic of the service they changed
Interaction with databases and messaging systems
API interactions (contract & functionality) with producer services the service depends on
API interactions its consumer services rely on
As a result of the increased complexity of API interactions, unit tests followed by end-to-end API tests in CI are no longer sufficient for microservices applications. Shifting testing left to achieve velocity is already well established. With microservices, shift-left is essential in order to manage the complexity as well. The integration of each service with its immediate producers and consumers should be tested as early as possible and in isolation. A new class of tests called Service tests have been added to the test pyramid to achieve this.
Developers should test their services as early as possible to validate that their changes haven’t disrupted any of the above interactions. And they should be able to perform this validation on demand during development. In addition, automated tests in CI should also validate the service interactions after builds.
Service tests enable each service to be tested in isolation and ensure that the functional integration with its producers and consumers continues to be correct. So, developers can rely on service tests to:
Validate that their changes to a service do not disrupt the business logic
Validate that the modified service still consumes their producers’ APIs correctly
Validate that consumers of their service are not going to be disrupted
Service tests can help shift integration testing left significantly. Otherwise, all the business logic relying on correct integration of services would happen much later in the SDLC. Therefore, service tests are included as a critical component in the test pyramid evangelized by Martin Fowler for distributed and micro-services architectures.
The primary challenge for service testing is the creation and maintenance of mocks. Mocks need to enable validation of functional correctness including contracts.
Reflect the contract between the consumer and the producer: Correctly reflecting the contract is an obvious need. However, managing contracts as both producer and consumer services evolve independently can get tedious.
Accurately represent expected values for validating functionality: Beyond the contract, functional validation requires ensuring that the actual business logic is correct. This requires a variety of test cases, ideally exercising all code paths with a variety of parametric combinations in API requests.
Context-sensitive mocks: Context sensitivity is often critical for accurate mocking. The same API request can return different results depending on the context of the request and the producer. For proper functional verification, the mocks must return responses that are context-dependent even if the requests are identical.
A critical requirement for solving the challenges above is to enable developers to create and maintain mocks of services with minimal additional engineering burden. In addition, in order to truly shift left, developers should be able to run the service test at the time of development.
Not place undue burden on developers: Mocks become stale as producer services change and require ongoing effort to keep them current. If the process requires a high level of manual effort, then it is almost certain that the mocks will go stale and become inaccurate quickly. A sustainable process should offer a high degree of automation.
Be easy to update when either the service or producer functionality changes: Service changes can either affect the consumer of a service, or change the requests made to the producer services.
Mesh Dynamics enables the creation of service tests with accurate mocks with minimal additional effort from developers. To learn more, please contact us at firstname.lastname@example.org.