Spring Boot WireMock - Testing HTTP clients
In this section, we'll develop a Spring Boot application that connects with a jsonplaceholder client with the help of RestClient and at the end we will perform integration testing with WireMock and JUnit 5.
jsonplaceholder
JSONPlaceholder is a free online REST API that we can use whenever we need some fake data.
RestClient
RestClient is a synchronous client to perform HTTP requests. It is a higher-order API since it performs HTTP requests by using an HTTP client library like the JDK HttpClient, Apache HttpComponents, and others. More Info - click here
WireMock
WireMock is a popular open-source library for API mock testing. More Info - click here
Advantages:
> No load on the remote server.
> Full control over the response.
> Works offline.
JUnit 5
For writing and running unit tests and integration tests for any Java application, the JUnit framework is a great option. It is already included in the spring-boot-starter-test module of Spring Boot 3.
The Spring Framework provides the following Rest clients for making calls to REST endpoints:
- RestClient (Supporting from Spring 6.1 onwards)
- WebClient
- HTTP Interface
- RestTemplate
In this example we are using RestClient client for making call to REST endpoint. We will implement a service that will fetch data from the JSONPlaceholder API.
Complete example
First, open the Spring initializr https://start.spring.io
Then, Provide the Group and Artifact name. We have provided Group name com.knf.dev.demo and Artifact spring-boot-wiremock-example. Here I selected the Maven project - language Java 17 - Spring Boot 3.3.1, Spring Web, and Lombok.
Then, click on the Generate button. When we click on the Generate button, it starts packing the project in a .zip(spring-boot-wiremock-example) file and downloads the project. Then, Extract the Zip file.
Then, import the project on your favourite IDE.
Final Project Directory
Then, add WireMock.
<dependency>
<groupId>com.maciejwalkowiak.spring</groupId>
<artifactId>wiremock-spring-boot</artifactId>
<version>2.1.2</version>
<scope>test</scope>
</dependency>
Complete 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>3.3.1</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.knf.dev.demo</groupId>
<artifactId>spring-boot-wiremock-example</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>spring-boot-wiremock-example</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>17</java.version>
</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>com.maciejwalkowiak.spring</groupId>
<artifactId>wiremock-spring-boot</artifactId>
<version>2.1.2</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</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>
application.properties
spring.application.name=spring-boot-wiremock-example
post.base.url = https://jsonplaceholder.typicode.com/posts
Base URL of post API(real) is declared inside application.properties.
Create RestClient
package com.knf.dev.demo.config;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestClient;
@Configuration
public class RestClientConfig {
@Value("${post.base.url}")
String baseUrl;
@Bean
RestClient restClient() {
return RestClient.create(baseUrl);
}
}
Create Post Model
package com.knf.dev.demo.model;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
public class Post {
public int id;
public String title;
public String body;
public int userId;
}
Create PostClient
package com.knf.dev.demo.client;
import com.knf.dev.demo.model.Post;
public interface PostClient {
Post findById(int id);
}
Create PostClientImpl.java
package com.knf.dev.demo.client;
import com.knf.dev.demo.model.Post;
import lombok.AllArgsConstructor;
import org.springframework.http.HttpStatusCode;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Component;
import org.springframework.web.client.RestClient;
@Component
@AllArgsConstructor
public class PostClientImpl implements PostClient {
private final RestClient restClient;
@Override
public Post findById(int id) {
Post post = restClient.get()
.uri("/{id}", id)
.accept(MediaType.APPLICATION_JSON)
.retrieve()
.onStatus(HttpStatusCode::is4xxClientError,
(req, res) -> {
// TODO
}
)
.onStatus(HttpStatusCode::is5xxServerError,
(req, res) -> {
//TODO
})
.body(Post.class);
return post;
}
}
Application.java
package com.knf.dev.demo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
Basic Stub - Create get_post_with_id_1.json
{
"request": {
"method": "GET",
"url": "/1"
},
"response": {
"status": 200,
"jsonBody": {
"userId": 1,
"id": 1,
"title": "Spring Boot Wire Mock Example",
"body": "Testing HTTP clients in Spring Boot app with wiremock"
},
"headers": {
"Content-Type": "application/json"
}
}
}
Create PostClientTest.java
package com.knf.dev.demo;
import com.knf.dev.demo.client.PostClient;
import com.knf.dev.demo.model.Post;
import com.maciejwalkowiak.wiremock.spring.ConfigureWireMock;
import com.maciejwalkowiak.wiremock.spring.EnableWireMock;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import static org.assertj.core.api.Assertions.assertThat;
@SpringBootTest
@EnableWireMock({
@ConfigureWireMock(name = "post-client", property = "post.base.url")
})
public class PostClientTest {
@Autowired
private PostClient postClient;
@Test
void shouldGetPost_whenFetchingWithValidId() {
Post post = postClient.findById(1);
assertThat(post.getId()).isEqualTo(1);
assertThat(post.getTitle()).isEqualTo("Spring Boot Wire Mock Example");
assertThat(post.getBody()).isEqualTo("Testing HTTP clients in " +
"Spring Boot app with wiremock");
assertThat(post.getUserId()).isEqualTo(1);
}
}
- @EnableWireMock adds test context customizer and enables WireMockSpringExtension
- @ConfigureWireMock creates a WireMockServer and passes the WireMockServer#baseUrl to a Spring environment property with a name given by property.
Run the Test
Or you can run the test using following command:
mvn test -Dtest=PostClientTest