Enterprise Java

WireMock with Spring Boot Example

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

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
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

1
2
3
4
5
spring.application.name=wiremock-demo
 
 
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

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
<?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

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
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

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
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

1
2
3
curl  -X GET \
  --header 'Accept: */*'

Here is the response.

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

01
02
03
04
05
06
07
08
09
10
{
  "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

1
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

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
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

001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
021
022
023
024
025
026
027
028
029
030
031
032
033
034
035
036
037
038
039
040
041
042
043
044
045
046
047
048
049
050
051
052
053
054
055
056
057
058
059
060
061
062
063
064
065
066
067
068
069
070
071
072
073
074
075
076
077
078
079
080
081
082
083
084
085
086
087
088
089
090
091
092
093
094
095
096
097
098
099
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
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

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
Notify of
guest


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

0 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
Back to top button