QA testing for microservices

Tips for building a low cost, high efficient, and automated mock system

Coupang Engineering
Coupang Engineering Blog
5 min readDec 17, 2019

--

By Eddie Ying

This post is also available in Korean.

The main goal of QA testing is to identity any bugs in the software and help to release the product successfully. While there are many types of testing like functional tests, interface tests, and performance tests, the fundamental stays the same. The process of testing is crucial to ensure software quality. We need accurate, relevant, and high-quality data that covers all scenarios. We must also use generated mock data instead of production data to ensure stability but consider the efficiency of creating and managing such data. We may also need to do so for high-level services that tend to have many dependencies and strongly coupled with other services. It may feel like running into a wall.

In this post, we will share some ideas about building a mock system that is low in cost and highly efficient for services with many dependencies and frequent changes.

Mocking in a microservices environment

Today, many companies have a microservice architecture and implement contract testing, which is a way of testing that two microservices communicate with each other according to the agree contract. Development takes place independently in compliance with the contract, prior to joint debugging or integration. Commonly, a mock service is used to produce rich data based on the contract for the corresponding provider or consumer so that it can cover all branches of the code and ensure the quality of service.

Example diagram of contract testing
Figure 1. Example of contract testing

This is a relatively easy and highly efficient testing method for lower-level services with low dependencies. However, what if we had more than 10 lower-level services with dependency on higher-level services? It would not only require a huge investment of time and resources to create a mock of all 10 services, but also require high maintenance costs for responding to changes. We need a more effective testing method for higher-level services; and one solution would be to mock an interface of a service without affecting other interfaces.

API gateway is the way

API gateway is a reverse proxy and a single entry point for all calls. Capturing requests and returns here would enable dynamic handling and enhance flexibility.

Example of API gateway in the architecture for testing
Figure 2. Example of API gateway in the architecture for testing

However, this sounds good in theory but is not practical to implement for mature frameworks in production. There is just too much cost and risk involved for modifying the existing code and architecture that has been tested and verified. A better solution would be to add a small block to this building blocks of architecture.

Example of a dedicated API gateway in the architecture for testing
Figure 3. Example of a dedicated API gateway in the architecture for testing

As shown in figure 3, a dedicated gateway like Django can be added to the existing test link. As long as the API gateway continues to handle all routing and forwarding requests, we can intercept requests and returns en route and edit the returns as mock data.

Types of mock data

Here are some types of mock data you can consider to generate or modify.

● Full returns: During the development, a new interface is needed for a dependent service. If the interface is being developed in parallel, mocking the entire interface of the dependent service could be a great option.

● Interface abnormality or return delay: This can be used for destructive testing and for testing to process delayed returns of an interface.

● Adding a partial field to the interface that responds: This is used to add a new field to a depending interface.

● Modifying the content of the field returned by the interface: Test data requirements can be satisfied by modifying the values to be returned by the required field. The benefit of partial modification compared to the full mock of the interface is that mock data doesn’t have to be adjusted every time.

● Deleting fields returned by the interface: This can quickly meet the test requirements in a test to validate a certain field of the interface.

● Modifying the body of an interface request: At times, you may need to modify the content of the requested body dynamically to meet the test requirement.

● Modifying the header of interface request: Add, modify, delete the requested header dynamically.

Grouping requests

It’s possible to have multiple requests come to a test server concurrently by multiple users, such as a when conducting a normal functional test and an automated test. To identify which request to mock, you can consider the users’ UIDs to the request header. When a request calls a dependent interface, add a UID to the request and group them based on the UID. Then you can use the requested URL, method, request parameter, and body to identify the specific interface to mock.

Example of a dedicated API gateway in the architecture for testing that uses javaagent for adding UID
Figure 4. Example of a dedicated API gateway in the architecture for testing that uses javaagent for adding UID

Conclusion

In this post, I wanted to share some ideas about how you can implement a mock system of this kind. I hope that this post will provide a starting point for developing a system that creates test data for higher-level services with a perspective on time to market, cost, and risk.

We’re actively looking for passionate individuals who are not afraid to ask questions, challenge the norm, and make things happen. Check out our open positions or sign up for job alerts!

--

--

Coupang Engineering
Coupang Engineering Blog

We write about how our engineers build Coupang’s e-commerce, food delivery, streaming services and beyond.