Efficient Test Suite Using MockServer and TestContainers




MOCKSERVER

Before going on to mockserver one needs to understand what a server is.

Going by the textbook definition, “A web server is a device that accepts the request from the user and gives back the response corresponding to that request.”

That’s all you need to know before getting started with MockServer.

What is a MockSever?

MockServer is made up of two words: MOCK + SERVER and mock means something which is not real.

MockServer is a fake server that acts as a real server to help the users in testing and checking their APIs and API responses. 
Using mockserver we can map the request and response, what this means is that basically, we can define what should be the response body that mockserver should send if it receives a specific request body. This is also called setting up the expectations.

When mockserver receives a request, it matches the request against active expectations that we have configured, if no matches are found it returns the status code 404 by default, but if the match is found it returns the response action which we have set up in the expectation.

Why use MockServer?

A widespread use case when one should use a mockserver for testing is when the project has a lot of dependency on 3rd party APIs. It is infrequent that the API signature will change, so you can simply simulate the request and response bodies, and you are all set to test your code. Isn’t that amazing?

Let’s have a look at some of the advantages of using mockserver: 

  • We can create all types of requests/responses for external 3rd party HTTP services called from our backend
  • Set up expectations independently for every test case
  • In single expectation we can create happy/failure path using if-else condition

Let’s understand this better with the help of an example:

Suppose there is an endpoint in your controller “/get-aadhaar-details”, that takes as input the user’s Aadhaar Number and consumes a 3rd party API which returns you details such as the name, date of birth, and mobile number of the user to whom the aadhaar number belongs, and status code 200 OK is returned if successful.

The request and response bodies are something like this:

To test the working of the “/get-aadhaar-details” endpoint we will have to consume the 3rd party API every time we run the test. But that is not the ideal way of testing. So what is the way out? Mockserver can be used for this purpose.

Simply define:

Using this we create our own expectation and the mockserver starts acting as a real server for the controller endpoint.

How does a MockServer work?

There are 3 steps involved in working of a mockserver:

  1. Receive request
  2. Set up the expectations
  3. Send response

In the setting up of expectations stage we can set up our request and response expectations using the following:

  1. Request Properties Matcher:
    • Method (POST, PUT, GET, etc)
    • Path (path of 3rd party API)
    • Path Parameters
    • Query String Parameters
    • Headers
    • Body
  2. Response Actions (Mock Responses):
    • Status Code (2xx, 3xx, 4xx)
    • Body
    • Header
    • Content Type

Let’s have a look at the code snippet to have a better understanding: 

Let’s have a high-level explanation of the code snippet:

Request:

If there is POST call to “/get-aadhaar-details” with request body 

{
  “aadhaar_number”: “123487651298”
}

Response: 

Then respond with status code 200 OK, with response body

{ 
   “name” :  “Rahul Sen”,
   “dob” : “07/07/1999”, 
   “mobileNumber” : “9812345670”
} 

and header “Location” set to value “https://www.aadhaar-info.com”

This is how we set expectations and make mockserver behave as a real server. Here we don’t need to use the 3rd party API to get a response, instead, we make use of mockserver to generate a response and test our APIs successfully.

TESTCONTAINERS

What are Testcontainers?

  • Testcontainers is a Java library that supports JUnit tests, providing instances of common databases, Selenium web browsers, or anything else that runs on a docker container
  • We can build and simulate stateful dependencies that our application needs, for example, S3, PostgreSQL, SQS, etc
  • Using testcontainers we can invoke docker containers from the code directly for the lifecycle of tests, as soon as the test is triggered the docker containers will be up, and as soon as the test has executed, the docker containers will shut down
  • Testcontainer has a module for localstack, which is, “A fully functional AWS cloud stack” to test your application without actually using the cloud

The list of POM dependencies needed for the functionality of testcontainers:

1. Core Functionality<dependency>
    <groupId>org.testcontainers</groupId>
    <artifactId>junit-jupiter</artifactId>
    <version>1.17.2</version>
    <scope>test</scope>
</dependency>
2.PostgreSQL<dependency>
    <groupId>org.testcontainers</groupId>
    <artifactId>postgresql</artifactId>
    <version>1.17.2</version>
    <scope>test</scope>
</dependency>
3. Localstack<dependency>
    <groupId>org.testcontainers</groupId>
    <artifactId>localstack</artifactId>
    <version>1.17.2</version>
    <scope>test</scope>
</dependency>

Why did we decide to use MockServer and Testcontainers?

As mentioned above, mockserver can be used for testing when the project has a lot of dependency on 3rd party APIs. 

In our case we had a lot of dependencies on 3rd party APIs for generating OTP, verifying OTP, creating accounts, etc, and a lot of these APIs such as verifying OTP and creating accounts required user intervention

Hence we decided to go forward with using mockserver for testing.

Benefits we got by using mockserver:

  • Catch bugs at an early stage such as race conditions and breaking functionality
  • We were able to refactor code without breaking the functionality
  • We were able to achieve a test coverage of more than 80%

One of the cons is that, if the API signature changes, the tests need to change as well, but in our case, it was a rare event.

We used testcontainers for PostgreSQL and localstack. 

These containers are invoked for the test life cycle, and automatically get shut down as the test execution is completed. We do not need to spin docker-compose separately, but all of it can be done via the test suite. 

Another advantage we get using localstack is that it integrated well with CI systems.

References

Tools used for making Images

Chinmay Somani
Chinmay Somani