VOOZH about

URL: https://www.javacodegeeks.com/wiremock-with-spring-boot-example.html

⇱ WireMock with Spring Boot Example - Java Code Geeks


1. Introduction

WireMock is a powerful HTTP mock server that stubs and verifies HTTP requests. It provides a controlled test environment, ensuring integration tests are fast, repeatable, and independent of external systems. In this example, I will demonstrate how to integrate WireMock into a Spring Boot project so the tests pass even when the third-party services become unavailable or return unexpected data.

2. Setup

In this step, I will create a gradle project with WireMock, spring-cloud-starter-contract-stub-runner, Lombok, and spring-boot-starter-web libraries via Spring Initializr.

πŸ‘ spring boot wiremock
Figure 1. Create a Spring Boot Project

2.1 Build.gradle

No modification for the generated build.gradle file.

build.gradle

plugins {
	id 'java'
	id 'org.springframework.boot' version '3.4.4'
	id 'io.spring.dependency-management' version '1.1.7'
}

group = 'com.zheng.demo'
version = '0.0.1-SNAPSHOT'

java {
	toolchain {
		languageVersion = JavaLanguageVersion.of(17)
	}
}

configurations {
	compileOnly {
		extendsFrom annotationProcessor
	}
}

repositories {
	mavenCentral()
}

ext {
	set('springCloudVersion', "2024.0.1")
}

dependencies {
	implementation 'org.springframework.boot:spring-boot-starter-web'
	compileOnly 'org.projectlombok:lombok'
	annotationProcessor 'org.projectlombok:lombok'
	testImplementation 'org.springframework.boot:spring-boot-starter-test'
	testImplementation 'org.springframework.cloud:spring-cloud-starter-contract-stub-runner'
	testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
}

dependencyManagement {
	imports {
		mavenBom "org.springframework.cloud:spring-cloud-dependencies:${springCloudVersion}"
	}
}

tasks.named('test') {
	useJUnitPlatform()
}

2.2 Application.properties

Updated the generated application.properties file to include the logging level and the third-party host URI.

application.properties

spring.application.name=wiremock-demo

3rdParth.host=https://api.restful-api.dev/objects

logging.level.org.zheng.demo=DEBUG
  • Line 3: The third-party API is a free API with a limit of 100 requests per day. Clients will receive 405 Method Not Allowed when exceeding the limit.

2.3 logback.xml

Added the logback.xml to enable the logging for the spring boot application.

logback.xml

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
	<include resource="org/springframework/boot/logging/logback/defaults.xml" />
	<include
		resource="org/springframework/boot/logging/logback/console-appender.xml" />

	<root level="INFO">
		<appender-ref ref="CONSOLE" />
	</root>

	<logger name="com.zheng.demo" level="DEBUG" additivity="false">
		<appender-ref ref="CONSOLE" />

	</logger>
</configuration>
  • Line 11: enable the log level to DEBUG.

2.4 Generated WiremockDemoApplication.java

Added a RestTempalte bean in the generated WiremockDemoApplication.java.

WiremockDemoApplication.java

package com.zheng.demo.wiremock_demo;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;

@SpringBootApplication
public class WiremockDemoApplication {

	public static void main(String[] args) {
		SpringApplication.run(WiremockDemoApplication.class, args);
	}

	@Bean
	public RestTemplate restTemplate() {
		return new RestTemplate();
	}

}

3. Java Model Object

3.1 ProductDetail.java

In this step, I will create a ProductDetail.java class that matches the Rest API response.

ProductDetail.java

package com.zheng.demo.wiremock_demo.model;

import com.fasterxml.jackson.annotation.JsonProperty;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class ProductDetail {

	private int year;
	private double price;
	@JsonProperty("CPU model")
	private String cpuModel;
	@JsonProperty("Hard disk size")
	private String hardDiskSize;

}

3.2 DemoObject.java

In this step, I will create a DemoObject.java class that matches the Rest API response.

DemoObject.java

package com.zheng.demo.wiremock_demo.model;

import java.time.LocalDateTime;

import com.fasterxml.jackson.annotation.JsonInclude;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
@JsonInclude(JsonInclude.Include.NON_EMPTY)
public class DemoObject {
	private String id;
	private String name;
	private ProductDetail data;
	
	private LocalDateTime updatedAt;
}

4. RestDemoController

In this step, I will create a RestDemoController.java that invokes the third-party API for the CRUD operations.

RestDemoController.java

package com.zheng.demo.wiremock_demo.rest;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpStatusCode;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;

import com.zheng.demo.wiremock_demo.model.DemoObject;

@RestController
@RequestMapping("/demo")
public class RestDemoController {

	private static final String PATH = "/";

	private static final Logger logger = LoggerFactory.getLogger(RestDemoController.class);

	@Value("${3rdParth.host}")
	private String thirdParthRestApiUrlBase;

	private final RestTemplate restTemplate;

	public RestDemoController(RestTemplate externalRestClient) {
		super();
		this.restTemplate = externalRestClient;
	}

	@GetMapping("/{id}")
	public DemoObject getObjectById(@PathVariable("id") int id) {
		String url = thirdParthRestApiUrlBase + PATH + id;
		logger.info("getObjectById url=" + url);
		return restTemplate.getForObject(url, DemoObject.class);
	}

	@PostMapping
	public DemoObject createObject(@RequestBody DemoObject obj) {
		logger.info("createObject url=" + thirdParthRestApiUrlBase);
		return restTemplate.postForObject(thirdParthRestApiUrlBase, obj, DemoObject.class);
	}

	@PutMapping("/{id}")
	public ResponseEntity<DemoObject> updateObject(@PathVariable("id") int id, @RequestBody DemoObject obj) {
		String url = thirdParthRestApiUrlBase + PATH + id;
		logger.info("updateObject url=" + url);
		restTemplate.put(url, obj, DemoObject.class);
		return ResponseEntity.ok().body(getObjectById(id));
	}

	@DeleteMapping("/{id}")
	public ResponseEntity<Object> deleteObjectById(@PathVariable("id") int id) {
		String url = thirdParthRestApiUrlBase + PATH + id;
		logger.info("deleteObjectById url=" + url);
		restTemplate.delete(url);
		return ResponseEntity.status(HttpStatusCode.valueOf(204)).build();
	}

}
  • Line 28, 29: defines the thirdParthRestApiUrlBase varaible that gets the value from 3rdParth.host property in the application.properties file.
  • Line 41, 47, 54, 62: prints out the logging for the third-party url. This will be used in 4.1 and 5.1.

4.1 Test the Spring Boot Application

In this step, I will start the spring boot application and test the Rest API via a curl command.

curl command for http://localhost:8080/demo/7

curl -X GET \
 'http://localhost:8080/demo/7' \
 --header 'Accept: */*' 

Here is the response.

http://localhost:8080/demo/7 Response

{
 "id": "7",
 "name": "Apple MacBook Pro 16",
 "data": {
 "year": 2019,
 "price": 1849.99,
 "CPU model": "Intel Core i9",
 "Hard disk size": "1 TB"
 }
}

Monitor the spring boot application and capture the server log.

application log

2025-04-05T08:58:45.525-05:00 INFO 12028 --- [wiremock-demo] [nio-8080-exec-2] c.z.d.w.rest.RestDemoController : getObjectById url=https://api.restful-api.dev/objects/7
  • Line 1: the application is hitting the actual third-party service – https://api.restful-api.dev/objects/7.

5. Spring boot Wiremock Test

In this step, I will create a RestDemoControllerTest.java that stubs the CRUD options of the third-party API with a mock server.

RestDemoControllerTest.java

package com.zheng.demo.wiremock_demo.rest;

import static com.github.tomakehurst.wiremock.client.WireMock.aResponse;
import static com.github.tomakehurst.wiremock.client.WireMock.delete;
import static com.github.tomakehurst.wiremock.client.WireMock.equalToJson;
import static com.github.tomakehurst.wiremock.client.WireMock.get;
import static com.github.tomakehurst.wiremock.client.WireMock.post;
import static com.github.tomakehurst.wiremock.client.WireMock.put;
import static com.github.tomakehurst.wiremock.client.WireMock.stubFor;
import static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.cloud.contract.wiremock.AutoConfigureWireMock;
import org.springframework.http.ResponseEntity;
import org.springframework.test.util.ReflectionTestUtils;

import com.zheng.demo.wiremock_demo.model.DemoObject;
import com.zheng.demo.wiremock_demo.model.ProductDetail;

@SpringBootTest
@AutoConfigureWireMock(port = 8090)
class RestDemoControllerTest {

	private static final String OBJECT_JSON = "{\"id\":\"7\",\"name\":\"Apple MacBook Pro 16\",\"data\":{\"year\":2019,\"price\":1849.99,\"CPU model\":\"Intel Core i9\",\"Hard disk size\":\"1 TB\"}}";
	@Autowired
	private RestDemoController testClass;

	@BeforeEach
	void setup() {
		ReflectionTestUtils.setField(testClass, "thirdParthRestApiUrlBase", "http://localhost:8090");
	}

	@Test
	void test_getObjectById() {
		stubFor(get(urlEqualTo("/7")).willReturn(
				aResponse().withStatus(200).withHeader("Content-Type", "application/json").withBody(OBJECT_JSON)));
		DemoObject rep = testClass.getObjectById(7);

		assertEquals("7", rep.getId());
	}

	@Test
	void test_createObject() {
		stubFor(post(urlEqualTo("/")).withRequestBody(equalToJson(OBJECT_JSON)).willReturn(
				aResponse().withStatus(200).withHeader("Content-Type", "application/json").withBody(OBJECT_JSON)));
		ProductDetail pd = ProductDetail.builder().year(2019).price(1849.99).cpuModel("Intel Core i9")
				.hardDiskSize("1 TB").build();
		DemoObject obj = DemoObject.builder().id("7").name("Apple MacBook Pro 16").data(pd).build();
		DemoObject rep = testClass.createObject(obj);
		assertEquals("7", rep.getId());
	}

	@Test
	void test_putObject() {
		stubFor(put(urlEqualTo("/7")).withRequestBody(equalToJson(OBJECT_JSON))
				.willReturn(aResponse().withStatus(204)));
		stubFor(get(urlEqualTo("/7")).willReturn(
				aResponse().withStatus(200).withHeader("Content-Type", "application/json").withBody(OBJECT_JSON)));
		ProductDetail pd = ProductDetail.builder().year(2019).price(1849.99).cpuModel("Intel Core i9")
				.hardDiskSize("1 TB").build();
		DemoObject obj = DemoObject.builder().id("7").name("Apple MacBook Pro 16").data(pd).build();
		ResponseEntity<DemoObject> rep = testClass.updateObject(7, obj);
		assertEquals("7", rep.getBody().getId());
	}

	@Test
	void test_deleteObject() {
		stubFor(delete(urlEqualTo("/7")).willReturn(aResponse().withStatus(204)));

		ResponseEntity<Object> rep = testClass.deleteObjectById(7);
		assertTrue(rep.getStatusCode().is2xxSuccessful());
	}

}
  • Line 26: @AutoConfigureWireMock(port = 8090) starts the mock server at port 8090.
  • Line 35: use the ReflectionTestUtilsβ€˜s setField method to set the testClass.thirdParthRestApiUrlBase to the mock server: http://localhost:8090.
  • Line 40, 49, 60, 62, 73: use WireMock to stub Get, Post, Put, and Delete operations’ requests and responses.

5.1 Execute Spring boot Wiremock Test

In this step, I will execute the RestDemoControllerTest.java and capture the console log.

πŸ‘ spring boot wiremock
Figure 2. Test Results

Mock Testing Console Log

2025-04-05T10:16:26.920-05:00 INFO --- [ main] t.c.s.AnnotationConfigContextLoaderUtils : Could not detect default configuration classes for test class [com.zheng.demo.wiremock_demo.rest.RestDemoControllerTest]: RestDemoControllerTest does not declare any static, non-private, non-final, nested classes annotated with @Configuration.
2025-04-05T10:16:27.090-05:00 INFO --- [ main] .b.t.c.SpringBootTestContextBootstrapper : Found @SpringBootConfiguration com.zheng.demo.wiremock_demo.WiremockDemoApplication for test class com.zheng.demo.wiremock_demo.rest.RestDemoControllerTest

 . ____ _ __ _ _
 /\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/ ___)| |_)| | | | | || (_| | ) ) ) )
 ' |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/

 :: Spring Boot :: (v3.4.4)

2025-04-05T10:16:27.514-05:00 INFO 27828 --- [wiremock-demo] [ main] c.z.d.w.rest.RestDemoControllerTest : Starting RestDemoControllerTest using Java 17.0.11 with PID 27828 (started by azpm0 in C:\MaryTools\workspace\wiremock-demo)
2025-04-05T10:16:27.514-05:00 DEBUG 27828 --- [wiremock-demo] [ main] c.z.d.w.rest.RestDemoControllerTest : Running with Spring Boot v3.4.4, Spring v6.2.5
2025-04-05T10:16:27.515-05:00 INFO 27828 --- [wiremock-demo] [ main] c.z.d.w.rest.RestDemoControllerTest : No active profile set, falling back to 1 default profile: "default"
2025-04-05T10:16:29.710-05:00 INFO 27828 --- [wiremock-demo] [ main] c.z.d.w.rest.RestDemoControllerTest : Started RestDemoControllerTest in 2.467 seconds (process running for 3.726)
2025-04-05T10:16:30.592-05:00 INFO 27828 --- [wiremock-demo] [ main] c.z.d.w.rest.RestDemoController : deleteObjectById url=http://localhost:8090/7
2025-04-05T10:16:30.758-05:00 INFO 27828 --- [wiremock-demo] [tp2061865206-32] WireMock : Request received:
127.0.0.1 - DELETE /7

Accept: [*/*]
User-Agent: [Java/17.0.11]
Host: [localhost:8090]
Connection: [keep-alive]
Content-Type: [application/x-www-form-urlencoded]
Transfer-Encoding: [chunked]



Matched response definition:
{
 "status" : 204
}

Response:
HTTP/1.1 204
Matched-Stub-Id: [45e076f0-5261-4a0b-9dbb-97b2e8e9b793]

2025-04-05T10:16:30.797-05:00 INFO 27828 --- [wiremock-demo] [ main] c.z.d.w.rest.RestDemoController : updateObject url=http://localhost:8090/7
2025-04-05T10:16:30.862-05:00 INFO 27828 --- [wiremock-demo] [tp2061865206-30] WireMock : Request received:
127.0.0.1 - PUT /7

Content-Type: [application/json]
Accept: [*/*]
User-Agent: [Java/17.0.11]
Host: [localhost:8090]
Connection: [keep-alive]
Transfer-Encoding: [chunked]
{"id":"7","name":"Apple MacBook Pro 16","data":{"year":2019,"price":1849.99,"CPU model":"Intel Core i9","Hard disk size":"1 TB"}}


Matched response definition:
{
 "status" : 204
}

Response:
HTTP/1.1 204
Matched-Stub-Id: [38f980f5-973c-4607-8af4-88fdc4e6750b]

2025-04-05T10:16:30.863-05:00 INFO 27828 --- [wiremock-demo] [ main] c.z.d.w.rest.RestDemoController : getObjectById url=http://localhost:8090/7
2025-04-05T10:16:30.895-05:00 INFO 27828 --- [wiremock-demo] [tp2061865206-32] WireMock : Request received:
127.0.0.1 - GET /7

Accept: [application/json, application/yaml, application/*+json]
User-Agent: [Java/17.0.11]
Host: [localhost:8090]
Connection: [keep-alive]



Matched response definition:
{
 "status" : 200,
 "body" : "{\"id\":\"7\",\"name\":\"Apple MacBook Pro 16\",\"data\":{\"year\":2019,\"price\":1849.99,\"CPU model\":\"Intel Core i9\",\"Hard disk size\":\"1 TB\"}}",
 "headers" : {
 "Content-Type" : "application/json"
 }
}

Response:
HTTP/1.1 200
Content-Type: [application/json]
Matched-Stub-Id: [d60084b6-7f40-4f79-98e0-d25a4d2ee2ed]

2025-04-05T10:16:30.911-05:00 INFO 27828 --- [wiremock-demo] [ main] c.z.d.w.rest.RestDemoController : getObjectById url=http://localhost:8090/7
2025-04-05T10:16:30.913-05:00 INFO 27828 --- [wiremock-demo] [tp2061865206-30] WireMock : Request received:
127.0.0.1 - GET /7

Accept: [application/json, application/yaml, application/*+json]
User-Agent: [Java/17.0.11]
Host: [localhost:8090]
Connection: [keep-alive]



Matched response definition:
{
 "status" : 200,
 "body" : "{\"id\":\"7\",\"name\":\"Apple MacBook Pro 16\",\"data\":{\"year\":2019,\"price\":1849.99,\"CPU model\":\"Intel Core i9\",\"Hard disk size\":\"1 TB\"}}",
 "headers" : {
 "Content-Type" : "application/json"
 }
}

Response:
HTTP/1.1 200
Content-Type: [application/json]
Matched-Stub-Id: [0930abe7-4bec-4762-90f8-9989ec165b4d]

2025-04-05T10:16:30.920-05:00 INFO 27828 --- [wiremock-demo] [ main] c.z.d.w.rest.RestDemoController : createObject url=http://localhost:8090
2025-04-05T10:16:30.934-05:00 INFO 27828 --- [wiremock-demo] [tp2061865206-32] WireMock : Request received:
127.0.0.1 - POST /

Accept: [application/json, application/yaml, application/*+json]
Content-Type: [application/json]
User-Agent: [Java/17.0.11]
Host: [localhost:8090]
Connection: [keep-alive]
Transfer-Encoding: [chunked]
{"id":"7","name":"Apple MacBook Pro 16","data":{"year":2019,"price":1849.99,"CPU model":"Intel Core i9","Hard disk size":"1 TB"}}


Matched response definition:
{
 "status" : 200,
 "body" : "{\"id\":\"7\",\"name\":\"Apple MacBook Pro 16\",\"data\":{\"year\":2019,\"price\":1849.99,\"CPU model\":\"Intel Core i9\",\"Hard disk size\":\"1 TB\"}}",
 "headers" : {
 "Content-Type" : "application/json"
 }
}

Response:
HTTP/1.1 200
Content-Type: [application/json]
Matched-Stub-Id: [97a6cc5b-8a01-4ad4-8301-6c56bf32b790]

2025-04-05T10:16:30.938-05:00 WARN 27828 --- [wiremock-demo] [ main] o.s.c.c.w.WireMockTestExecutionListener : You've used fixed ports for WireMock setup - will mark context as dirty. Please use random ports, as much as possible. Your tests will be faster and more reliable and this warning will go away
  • Highlighted lines print the mock server URL: localhost:8090 instead of the actual services.

6. Conclusion

Testing external dependencies such as REST APIs can be challenging when developing web applications. Making network calls is slow and unreliable. WireMock provides a controlled test environment, ensuring integration tests are fast, repeatable, and independent of external systems. In this example, I created a spring boot web application that utilized Wiremock to stub the third-party API requests and responses.

7. Download

This was an example of a gradle project that included wiremock in a spring boot application.

Download
You can download the full source code of this example here: WireMock with Spring Boot Example
Do you want to know how to develop your skillset to become a Java Rockstar?
Subscribe to our newsletter to start Rocking right now!
To get you started we give you our best selling eBooks for FREE!
1. JPA Mini Book
2. JVM Troubleshooting Guide
3. JUnit Tutorial for Unit Testing
4. Java Annotations Tutorial
5. Java Interview Questions
6. Spring Interview Questions
7. Android UI Design
and many more ....
I agree to the Terms and Privacy Policy

Thank you!

We will contact you soon.

πŸ‘ Photo of Mary Zheng
Mary Zheng
April 9th, 2025Last Updated: April 9th, 2025
0 1,610 7 minutes read

Mary Zheng

Mary graduated from the Mechanical Engineering department at ShangHai JiaoTong University. She also holds a Master degree in Computer Science from Webster University. During her studies she has been involved with a large number of projects ranging from programming and software engineering. She worked as a lead Software Engineer where she led and worked with others to design, implement, and monitor the software solution.
Subscribe

This site uses Akismet to reduce spam. Learn how your comment data is processed.

0 Comments
Oldest
Newest Most Voted
Back to top button
Close
wpDiscuz