Show List

Create WireMock Stubs Using Spring Boot Application and JUnit

In this tutorial we will create WireMock stubs for a service used in a Spring Boot application. WireMock stubs can further be used to test the application independent of the service.

Here is the directory structure of the application we are going to build. This application displays list of universities in country by calling a downstream service API. In order to be able to test the application even when the down stream service API is down, we will use WireMock jar to create the service API stubs. This is a one time set up and created stubs can then be used to provide the response as if it was coming from the service API.:

Creating Spring Boot Application with Required Dependencies

Go to Spring initializr website and create a Java Maven Project with dependencies: Spring Web, Lombok. Download the project zip and extract to a folder.
Update pom.xml file to add dependency to springdoc-openapi-ui (To use swagger UI), wiremock-standalone (For WireMock server) and junit-jupiter-engine (For JUnit). 

pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.0</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.wiremock.demo</groupId>
<artifactId>demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>demo</name>
<description>Demo project for WireMock</description>
<properties>
<java.version>11</java.version>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>

<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-ui</artifactId>
<version>1.6.9</version>
</dependency>
<dependency>
<groupId>com.github.tomakehurst</groupId>
<artifactId>wiremock-standalone</artifactId>
<version>2.27.2</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<version>5.8.2</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.opentelemetry</groupId>
<artifactId>opentelemetry-sdk-extension-autoconfigure-spi</artifactId>
<version>1.15.0</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>

</project>

Adding DTO, Service, Control and Rest Template Class

The application would make use of service API to get the list of universities and display on the screen. Here is a sample response for one country http://universities.hipolabs.com/search?country=Monaco
[{"web_pages": ["http://www.riviera.fr/usehome.htm"], "state-province": null, "alpha_two_code": "MC", "name": "University of Southern Europe (Monaco Business School)", "country": "Monaco", "domains": ["riviera.fr"]}]
So based on the five fields present in the response, we can create the DTO (Data Transfer Object) class as below:

UniDTO
 package com.wiremock.demo.dto;

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonInclude;
import lombok.Data;

import java.util.List;

@Data
@JsonIgnoreProperties(ignoreUnknown = true)
@JsonInclude(JsonInclude.Include.NON_NULL)
public class UniDTO {
private String name;
private String country;
private String alpha_two_code;
private List<String> domains;
private List<String> web_pages;
}
Below is the service that will call the end point to get the data. A variable connectingAPIURL is used to read the end point url from the application yml file. When the application is running in production, it would be using the real service host url http://universities.hipolabs.com. When the application is being tested, it will be using WireMock service host http://localhost:9999 as end point for recording and playing back the stubs. 

UniService
package com.wiremock.demo.service;

import com.wiremock.demo.dto.UniDTO;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.http.*;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.util.UriComponentsBuilder;
import java.util.List;

@Service
public class UniService {
@Autowired
private RestTemplate restTemplate;

private String apiURL;

public UniService(@Value("${connectingAPIURL}") String apiURL) {
this.apiURL = apiURL;
}

public List<UniDTO> getUniInfo(String name) {
String urlTemplate = UriComponentsBuilder.fromHttpUrl(apiURL + "/search").queryParam("name", name).encode().toUriString();

System.out.println("Calling urlTemplate " + urlTemplate);;

ResponseEntity<List<UniDTO>> rateResponse =
restTemplate.exchange(urlTemplate,
HttpMethod.GET, null, new ParameterizedTypeReference<>() {
}, name);
return rateResponse.getBody();
}
}
Here is the application yml file that will be used when the application is running in production. It provides the value for real service url.
application.yml
connectingAPIURL: http://universities.hipolabs.com
Here is the yml file that will be used while running the application for test
application-iTest.yml
connectingAPIURL: http://localhost:9999
RestTemplate Bean used in the service is defined in the RestTemplateConfig file.

RestTemplateConfig.java
package com.wiremock.demo.config;

import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;

@Configuration
public class RestTemplateConfig {

@Bean
public RestTemplate restTemplate(RestTemplateBuilder builder) {
return builder.build();
}
}
WireMockConfig class has the configuration information for the WireMock server.

WiremockConfig.java
package com.wiremock.demo.config;

import lombok.Data;
import org.springframework.context.annotation.Configuration;

@Configuration
@Data
public class WireMockConfig {
private int port = 9999;
private boolean recording = true;
private String url = "http://universities.hipolabs.com";
}
Here is the controller for the application. It exposes Get endpoint at "/api/university" and calls the service to get the university details for the country.
UniController.java
package com.wiremock.demo.controller;

import com.wiremock.demo.dto.UniDTO;
import com.wiremock.demo.service.UniService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;

@RestController
public class UniController {

@Autowired
private UniService uniService;

@GetMapping("/api/university")
public List<UniDTO> getUniversitiesForCountry(@RequestParam String country) {
return uniService.getUniInfo(country);
}
}
DemoApplicationTests.java
This Test class is used record the stubs and then playback for test. Using the information from the WireMockConfig bean, WireMockServer is created and started at the start of the test.

getUniversitiesForCountry method is used to call the controller endpoint using MockMvC and get the universities in a country.

Notice that the class sets the active profile as "iTest" so application-iTest.yml file properties get into effect while running test from this call.
package com.wiremock.demo;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.github.tomakehurst.wiremock.WireMockServer;
import com.github.tomakehurst.wiremock.client.WireMock;
import com.github.tomakehurst.wiremock.common.ConsoleNotifier;
import com.github.tomakehurst.wiremock.core.WireMockConfiguration;
import com.github.tomakehurst.wiremock.matching.RequestPatternBuilder;
import com.github.tomakehurst.wiremock.recording.RecordSpec;
import com.github.tomakehurst.wiremock.recording.RecordingStatus;
import com.wiremock.demo.config.WireMockConfig;
import com.wiremock.demo.service.UniService;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.CsvSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.web.servlet.MockMvc;

import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;

@ActiveProfiles(value = "iTest")
@SpringBootTest
@AutoConfigureMockMvc
class DemoApplicationTests {

@Autowired
private MockMvc mockMvc;

@Autowired
private WireMockConfig proxy;

@Autowired
private UniService uniService;

protected final ObjectMapper objectMapper = new ObjectMapper();

WireMockServer wireMockServer;


@BeforeEach
void startRecording() {
wireMockServer = new WireMockServer(
WireMockConfiguration.options()
.port(proxy.getPort())
.notifier(new ConsoleNotifier(true))
);
wireMockServer.start();
if (proxy.isRecording()) {
wireMockServer.startRecording(config(proxy.getUrl(), true));
}
}

@AfterEach
void stopRecording() {
if (wireMockServer.getRecordingStatus().getStatus().equals(RecordingStatus.Recording)) {
wireMockServer.stopRecording();
}
wireMockServer.stop();

}


@ParameterizedTest
@CsvSource({"india"})
void getUniversitiesForCountry(String country) throws Exception {
String actualResponse = mockMvc.perform(get("/api/university")
.contentType("application/json")
.param("country", country)
)
.andExpect(status().isOk()).andReturn().getResponse().getContentAsString();
}

private RecordSpec config(String recordingURL, boolean recordingEnabled) {
return WireMock.recordSpec()
.forTarget(recordingURL)
.onlyRequestsMatching(RequestPatternBuilder.allRequests())
.captureHeader("Accept")
.makeStubsPersistent(recordingEnabled)
.ignoreRepeatRequests()
.matchRequestBodyWithEqualToJson(true, true)
.build();
}

}
Folder for storing the WireMock files
There are three folders created under test/resources: __files, mappings, recorded. 

These are default named folders used to store WireMock stubs

Recording the WireMock stub

Make sure that recording flag is set to true in the WireMockConfig.java class.

private boolean recording = true;

Now run the getUniversitiesForCountry test method in the DemoApplicationTests class.
Controller endpoint "/api/university" is invoked passing the country name. The controller calls getUniInfo method from UniService . The service reads connectingAPIURL from the application-iTest.yml file and uses WireMock server host to send the request.

Because we have set the WireMock server "recording" mode to true, WireMock server forwards the request to destination host http://universities.hipolabs.com and records request and response.

The recorded stub can be found under the test/resources folders and shown in the image below. 

Playing back the recorded WireMock stub

To play back the recorded stub, update the "recording" Boolean value to false in the WireMockConfig.java class.

private boolean recording = false;

Now rerun the getUniversitiesForCountry test method in the DemoApplicationTests class. This time the getUniInfo method in the UniService again sends the request to WireMock server host. Because the server is not in recording mode, it looks up the recorded files to provide matching response for the country.

Additional Note

In this example because we are running the WireMock server on the local host with http, we have used http (and not https) service for the demo. As we are using WireMock server as proxy for recording stub, redirection would work if WireMock server and the service use the same protocol i.e. both should be on http or both should be on https.

Source Code

Source Code:
https://github.com/it-code-lab/WireMockSpringBootdemo


    Leave a Comment


  • captcha text