Show List

Testing Spring Boot Application with Hoverfly and JUnit

In this tutorial we will create Hoverfly stubs for a service used in a Spring Boot application. The stubs will 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 a separately running Hoverfly server 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) 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.hoverfly.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>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.  

UniService
package com.hoverfly.demo.service;

import com.hoverfly.demo.dto.UniDTO;
import org.springframework.beans.factory.annotation.Autowired;
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 = "http://universities.hipolabs.com";


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();
}
}
RestTemplate Bean used in the service is defined in the RestTemplateConfig file. It reads the "appmode" variable from the yml file. If appmode is "proxy" it uses Hoverfly server as proxy for the requests received. For this demo, we have Hoverfly standalone server running at localhost standard port 8500. When the Hoverfly server is set on Capture mode, the request and response will be recorded. When the server is on Simulate mode, the request will be matched against the saved mocks and response will be provided by the Hoverfly server without calling the real service.

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

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.Environment;
import org.springframework.http.client.SimpleClientHttpRequestFactory;
import org.springframework.web.client.RestTemplate;

import java.net.InetSocketAddress;
import java.net.Proxy;
import java.net.Proxy.Type;

@Configuration
public class RestTemplateConfig {

private static final int HOVERFLY_PORT = 8500;
private static final String HOVERFLY_HOST = "localhost";
private static final String PROXY = "proxy";

@Autowired
private Environment env;

@Bean
public RestTemplate restTemplate(RestTemplateBuilder builder) {


String mode = env.getProperty("appmode") ;

System.out.println("##################### Mode ################# " + mode);

SimpleClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory();
Proxy proxy = new Proxy(java.net.Proxy.Type.HTTP, new InetSocketAddress(HOVERFLY_HOST, HOVERFLY_PORT));
requestFactory.setProxy(proxy);
RestTemplate template = null;

if (mode != null && mode.equalsIgnoreCase(PROXY)) {
System.out.println("######### Running in PROXY mode");
template = new RestTemplate(requestFactory);
} else {
System.out.println("######### Running in PRODUCTION mode");
template = new RestTemplate();
}

return template;
}
}
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
appmode: production
Here is the yml file that will be used while running the application for test
application-iTest.yml
appmode: proxy

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.hoverfly.demo.controller;

import com.hoverfly.demo.dto.UniDTO;
import com.hoverfly.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 can be used to record the stubs and then playback for 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.hoverfly.demo;


import com.hoverfly.demo.service.UniService;
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 UniService uniService;

@ParameterizedTest
@CsvSource({"Monaco"})
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();
}

}

Recording the Hoverfly stub

Make sure that Hoverfly mode is "Capture".

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 . Because the RestTemplate used in the service is configured to use Hoverfly server as proxy, the request goes through the Hoverfly server.

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

Playing back the recorded Hoverfly stub

To play back the recorded stub, update the Hoverfly mode to "Simulate".

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

Source Code

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


    Leave a Comment


  • captcha text