Integration Testing on Spring boot microservice with Wiremock and JUnit5

A software system can and has to be tested on different levels of abstraction. ISTQB (International Software Testing Qualifications Board) defines four different test levels: Component Testing (Unit Testing), Integration Testing, System Testing and Acceptance Testing.

In this story, we are going to perform integration tests between Spring Boot microservices using Wiremock.

What is integration testing ?

Integration tests are tests that include two or more integration components. It is used to expose defects in interfaces and in interactions between components or integrated systems.

Overview

Suppose we are building a microservice and need to access data from other microservices.

Our invoicing microservice needs customer data and access to the payment service.

The challenges of integration testing

When we need to perform our integration tests with other microservices, there are several challenges to resolve:

  • Microservices applications are generally composed of multiple services. External services might not always be available. We are heavily dependent on external systems and any downtime there will impact our tests and indirectly the development and delivery process.
  • In some cases, there are costs involved in hitting an API. For example, suppose our customer service propose 1000 requests per month. As integration tests are usually run during every regression (and most of the time with every commit), it might not be a cost-effective solution to hit such an API that costs us even for testing purposes.
  • External services might or might not have a test environment. For Example, our payment microservice might always require real data to fetch and return responses.
  • An external service can not be configured to return the desired response. Even if possible, you’ll have to create a lot of test data to ensure different responses for different request inputs.
    For Example, you want to test error scenarios like an microservice is returning different status codes for different types of data. Now as the response is not under our control, we will need to create multiple sets of data to validate different possible scenarios or outcomes.
Photo by David Travis on Unsplash

What is WireMock?

WireMock is a library for stubbing and mocking web services. It constructs a HTTP server that we could connect to as we would to an actual web service.

It can be used in two ways:

1- A Standalone Wiremock Server

The WireMock server can be run in its own process, and configured via the Java API, JSON over HTTP or JSON files. This is a good alternative when you want to host your standalone server on some machine and use it as a single mocking server for the entire project or team.

2- JUnit Rule Configuration

The Wiremock server can be used with JUnit tests as a JUnit rule configuration. With this, JUnit supports the life cycle of Wiremock, that is, Wiremock starts and stops.
It is mainly used in configurations where you want to start and stop the server after each test.

In this story, we will focus on configuring the JUnit rule with JSON files.

Get Started

Full billing service controller code.

RestController
@RequestMapping(path = "/invoice")
@Slf4j
public class InvoiceController {

private final InvoiceService invoiceService;

private final PaymentClient paymentClient;
private final CustomerClient customerClient;

public InvoiceController(InvoiceService invoiceService, PaymentClient paymentClient, CustomerClient customerClient) {
this.invoiceService = invoiceService;
this.paymentClient = paymentClient;
this.customerClient = customerClient;
}

@PostMapping
public ResponseEntity<Invoice> create(@RequestBody InvoiceRequestDTO invoicePost) {

Customer customer = customerClient.customerByCode(invoicePost.getCustomerCode());
Invoice invoice = Invoice.builder()
.dueDate(invoicePost.getDueDate())
.amount(invoicePost.getAmount())
.createdDate(LocalDateTime.now())
.customer(customer)
.number(RandomStringUtils.randomAlphanumeric(10))
.build();
Invoice invoiceCreated = invoiceService.save(invoice);

return new ResponseEntity<>(invoiceCreated, HttpStatus.CREATED);
}

@PostMapping("/payment")
public ResponseEntity<Void> savePayment(@RequestBody PaymentRequestDTO paymentRequestDTO) {

Optional<Invoice> invoiceOptional = invoiceService.getById(paymentRequestDTO.getInvoiceId());
if (invoiceOptional.isPresent()){
Invoice invoice = invoiceOptional.get();

PaymentDTO paymentDTO = PaymentDTO.builder()
.cardExpiryDate(paymentRequestDTO.getCardExpiryDate())
.cardNumber(paymentRequestDTO.getCardNumber())
.customerNumber(invoice.getCustomer().getCode())
.amount(paymentRequestDTO.getAmount())
.build();

String paymentId = paymentClient.savePayment(paymentDTO);

invoice.getPaymentIds().add(paymentId);

invoiceService.save(invoice);

return new ResponseEntity<>(HttpStatus.CREATED);

}else {
return new ResponseEntity<>(HttpStatus.BAD_REQUEST);
}
}

}

Setup integration test environment

  1. Adding test dependencies in billing Service pom.xml

2. Mappings JSON files

  • mappings: The mappings folder where all the JSON files that will be stored for the Wiremock server.
  • payload: JSON data sent in the body of billing service HTTP requests.

3. Integration testing class

Testing method

One of the strong points of Wiremock is its concept of Scenarios which allows to simulate the different states of a REST API. This allows us to create tests in which the API we are using behaves differently depending on the state it is in.

Suppose we need to test customer service for http 400, 404, and 401 responses.

{
  "mappings": [
    {
      "scenarioName": "error customer scenario",
      "requiredScenarioState": "Started",
      "request": {
        "method": "GET",
        "urlPath": "/api/customers/code/CUST02"
      },
      "response": {
        "status": 404
      }
    },
    {
      "scenarioName": "error customer scenario",
      "requiredScenarioState": "Started",
      "newScenarioState": "404 customer scenario",
      "request": {
        "method": "GET",
        "urlPath": "/api/customers/code/CUST03"
      },
      "response": {
        "status": 400
      }
    },
    {
      "scenarioName": "error customer scenario",
      "requiredScenarioState": "404 customer scenario",
      "request": {
        "method": "GET",
        "urlPath": "/api/customers/code/CUST04"
      },
      "response": {
        "status": 401
      }
    }
  ]
}

The initial state of any scenario is Scenario.STARTED.

Run tests

Summary

In this story, we have learned how to write a integration Testing on Spring boot microservice with Wiremock and JUnit5.

All source code is available on GitHub.

Thanks for reading!

References

👉 Link to Medium blog

Related Posts