Skip to content

Commit 473edf5

Browse files
Merge pull request #16750 from ukhan1980/bael-7903-wiremock-tests-with-web-client
[bael-7903]add code for wiremock with webclient article
2 parents a620f57 + 0240477 commit 473edf5

File tree

3 files changed

+308
-2
lines changed

3 files changed

+308
-2
lines changed

spring-reactive-modules/spring-reactive-4/pom.xml

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717

1818
<properties>
1919
<wiremock-standalone.version>3.4.2</wiremock-standalone.version>
20+
<wiremock-spring-cloud.version>4.1.2</wiremock-spring-cloud.version>
2021
</properties>
2122

2223
<dependencies>
@@ -30,13 +31,11 @@
3031
<artifactId>spring-boot-starter-test</artifactId>
3132
<scope>test</scope>
3233
</dependency>
33-
3434
<dependency>
3535
<groupId>io.projectreactor</groupId>
3636
<artifactId>reactor-test</artifactId>
3737
<scope>test</scope>
3838
</dependency>
39-
4039
<dependency>
4140
<groupId>io.netty</groupId>
4241
<artifactId>netty-all</artifactId>
@@ -48,6 +47,12 @@
4847
<version>${wiremock-standalone.version}</version>
4948
<scope>test</scope>
5049
</dependency>
50+
<dependency>
51+
<groupId>org.springframework.cloud</groupId>
52+
<artifactId>spring-cloud-contract-wiremock</artifactId>
53+
<scope>test</scope>
54+
<version>${wiremock-spring-cloud.version}</version>
55+
</dependency>
5156
</dependencies>
5257

5358
</project>
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
package com.baeldung.webclient;
2+
3+
import java.util.Objects;
4+
5+
public class WeatherData {
6+
7+
public WeatherData() {
8+
9+
}
10+
11+
public WeatherData(String city, int temperature, String description) {
12+
this.city = city;
13+
this.temperature = temperature;
14+
this.description = description;
15+
}
16+
17+
private String city;
18+
private int temperature;
19+
private String description;
20+
21+
public String getCity() {
22+
return city;
23+
}
24+
25+
public void setCity(String city) {
26+
this.city = city;
27+
}
28+
29+
public int getTemperature() {
30+
return temperature;
31+
}
32+
33+
public void setTemperature(int temperature) {
34+
this.temperature = temperature;
35+
}
36+
37+
public String getDescription() {
38+
return description;
39+
}
40+
41+
public void setDescription(String description) {
42+
this.description = description;
43+
}
44+
@Override
45+
public boolean equals(Object o) {
46+
if (this == o)
47+
return true;
48+
if (o == null || getClass() != o.getClass())
49+
return false;
50+
WeatherData that = (WeatherData) o;
51+
return temperature == that.temperature && Objects.equals(city, that.city) && Objects.equals(description,
52+
that.description);
53+
}
54+
55+
@Override
56+
public int hashCode() {
57+
return Objects.hash(city, temperature, description);
58+
}
59+
}
Lines changed: 242 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,242 @@
1+
package com.baeldung.webclient;
2+
3+
import static com.github.tomakehurst.wiremock.client.WireMock.aResponse;
4+
import static com.github.tomakehurst.wiremock.client.WireMock.equalTo;
5+
import static com.github.tomakehurst.wiremock.client.WireMock.stubFor;
6+
import static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo;
7+
import static com.github.tomakehurst.wiremock.client.WireMock.urlPathEqualTo;
8+
import static org.junit.Assert.assertEquals;
9+
import static org.junit.Assert.assertNotNull;
10+
import static org.junit.Assert.assertThrows;
11+
import static org.junit.Assert.assertTrue;
12+
13+
import java.util.concurrent.ThreadLocalRandom;
14+
15+
import org.junit.jupiter.api.Test;
16+
import org.junit.jupiter.api.extension.ExtendWith;
17+
import org.springframework.beans.factory.annotation.Autowired;
18+
import org.springframework.beans.factory.annotation.Value;
19+
import org.springframework.boot.test.context.SpringBootTest;
20+
import org.springframework.cloud.contract.wiremock.AutoConfigureWireMock;
21+
import org.springframework.http.HttpHeaders;
22+
import org.springframework.http.HttpStatus;
23+
import org.springframework.test.context.junit.jupiter.SpringExtension;
24+
import org.springframework.web.reactive.function.client.WebClient;
25+
import org.springframework.web.reactive.function.client.WebClientResponseException;
26+
27+
import com.github.tomakehurst.wiremock.client.WireMock;
28+
import com.github.tomakehurst.wiremock.stubbing.Scenario;
29+
30+
@ExtendWith(SpringExtension.class)
31+
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
32+
@AutoConfigureWireMock(port = 0)
33+
public class WeatherAPIIntegrationTest {
34+
35+
@Autowired
36+
private WebClient.Builder webClientBuilder;
37+
38+
@Value("${wiremock.server.port}")
39+
private int wireMockPort;
40+
41+
@Test
42+
public void givenWebClientBaseURLConfiguredToWireMock_whenGetRequestForACity_thenWebClientRecievesSuccessResponse() {
43+
stubFor(WireMock.get(urlEqualTo("/weather?city=London"))
44+
.willReturn(aResponse().withStatus(200)
45+
.withHeader("Content-Type", "application/json")
46+
.withBody("{\"city\": \"London\", \"temperature\": 20, \"description\": \"Cloudy\"}")));
47+
48+
WebClient webClient = webClientBuilder.baseUrl("http://localhost:" + wireMockPort)
49+
.build();
50+
51+
WeatherData weatherData = webClient.get()
52+
.uri("/weather?city=London")
53+
.retrieve()
54+
.bodyToMono(WeatherData.class)
55+
.block();
56+
57+
assertNotNull(weatherData);
58+
assertEquals("London", weatherData.getCity());
59+
assertEquals(20, weatherData.getTemperature());
60+
assertEquals("Cloudy", weatherData.getDescription());
61+
}
62+
63+
@Test
64+
public void givenWebClientBaseURLConfiguredToWireMock_whenGetRequestWithInvalidCity_thenExceptionReturnedFromWireMock() {
65+
stubFor(WireMock.get(urlEqualTo("/weather?city=InvalidCity"))
66+
.willReturn(aResponse().withStatus(404)
67+
.withHeader("Content-Type", "application/json")
68+
.withBody("{\"error\": \"City not found\"}")));
69+
70+
WebClient webClient = webClientBuilder.baseUrl("http://localhost:" + wireMockPort)
71+
.build();
72+
73+
// Fetch weather data for an invalid city
74+
WebClientResponseException exception = assertThrows(WebClientResponseException.class, () -> {
75+
webClient.get()
76+
.uri("/weather?city=InvalidCity")
77+
.retrieve()
78+
.bodyToMono(WeatherData.class)
79+
.block();
80+
});
81+
82+
assertEquals(HttpStatus.NOT_FOUND, exception.getStatusCode());
83+
}
84+
85+
@Test
86+
public void givenWebClientBaseURLConfiguredToWireMock_whenGetRequest_thenResponseReturnedWithDelay() {
87+
// Stubbing response with a delay
88+
stubFor(WireMock.get(urlEqualTo("/weather?city=London"))
89+
.willReturn(aResponse().withStatus(200)
90+
.withFixedDelay(1000) // 1 second delay
91+
.withHeader("Content-Type", "application/json")
92+
.withBody("{\"city\": \"London\", \"temperature\": 20, \"description\": \"Cloudy\"}")));
93+
94+
WebClient webClient = webClientBuilder.baseUrl("http://localhost:" + wireMockPort)
95+
.build();
96+
97+
long startTime = System.currentTimeMillis();
98+
WeatherData weatherData = webClient.get()
99+
.uri("/weather?city=London")
100+
.retrieve()
101+
.bodyToMono(WeatherData.class)
102+
.block();
103+
long endTime = System.currentTimeMillis();
104+
105+
assertNotNull(weatherData);
106+
assertTrue(endTime - startTime >= 1000); // Assert the delay
107+
}
108+
109+
@Test
110+
public void givenWebClientBaseURLConfiguredToWireMock_whenGetRequest_theCustomHeaderIsReturned() {
111+
// Stubbing response with custom headers
112+
stubFor(WireMock.get(urlEqualTo("/weather?city=London"))
113+
.willReturn(aResponse().withStatus(200)
114+
.withHeader("Content-Type", "application/json")
115+
.withHeader("X-Custom-Header", "foobar")
116+
.withBody("{\"city\": \"London\", \"temperature\": 20, \"description\": \"Cloudy\"}")));
117+
118+
WebClient webClient = webClientBuilder.baseUrl("http://localhost:" + wireMockPort)
119+
.build();
120+
121+
// Fetch weather data for London
122+
WeatherData weatherData = webClient.get()
123+
.uri("/weather?city=London")
124+
.retrieve()
125+
.bodyToMono(WeatherData.class)
126+
.block();
127+
128+
assertNotNull(weatherData);
129+
assertEquals("London", weatherData.getCity());
130+
131+
// Assert the custom header
132+
HttpHeaders httpHeaders = webClient.get()
133+
.uri("/weather?city=London")
134+
.exchange()
135+
.block()
136+
.headers()
137+
.asHttpHeaders();
138+
assertEquals("foobar", httpHeaders.getFirst("X-Custom-Header"));
139+
}
140+
141+
@Test
142+
public void givenWebClientBaseURLConfiguredToWireMock_whenGetRequest_theDynamicResponseIsSent() {
143+
// Generate a random temperature value between 10 and 30
144+
int temperature = ThreadLocalRandom.current()
145+
.nextInt(10, 31);
146+
147+
// Construct the response body with the generated temperature value
148+
String responseBody =
149+
"{\"city\": \"London\", \"temperature\": " + temperature + ", \"description\": \"Cloudy\"}";
150+
151+
// Stubbing response with dynamic temperature
152+
stubFor(WireMock.get(urlEqualTo("/weather?city=London"))
153+
.willReturn(aResponse().withStatus(200)
154+
.withHeader("Content-Type", "application/json")
155+
.withBody(responseBody)));
156+
157+
WebClient webClient = webClientBuilder.baseUrl("http://localhost:" + wireMockPort)
158+
.build();
159+
160+
WeatherData weatherData = webClient.get()
161+
.uri("/weather?city=London")
162+
.retrieve()
163+
.bodyToMono(WeatherData.class)
164+
.block();
165+
166+
// Assert temperature is within the expected range
167+
assertNotNull(weatherData);
168+
assertTrue(weatherData.getTemperature() >= 10 && weatherData.getTemperature() <= 30);
169+
}
170+
171+
@Test
172+
public void givenWebClientWithBaseURLConfiguredToWireMock_whenGetWithQueryParameter_thenWireMockReturnsResponse() {
173+
// Stubbing response with specific query parameters
174+
stubFor(WireMock.get(urlPathEqualTo("/weather"))
175+
.withQueryParam("city", equalTo("London"))
176+
.willReturn(aResponse()
177+
.withStatus(200)
178+
.withHeader("Content-Type", "application/json")
179+
.withBody("{\"city\": \"London\", \"temperature\": 20, \"description\": \"Cloudy\"}")));
180+
181+
WebClient webClient = webClientBuilder.baseUrl("http://localhost:" + wireMockPort).build();
182+
183+
WeatherData londonWeatherData = webClient.get()
184+
.uri(uriBuilder -> uriBuilder.path("/weather").queryParam("city", "London").build())
185+
.retrieve()
186+
.bodyToMono(WeatherData.class)
187+
.block();
188+
189+
assertNotNull(londonWeatherData);
190+
assertEquals("London", londonWeatherData.getCity());
191+
}
192+
193+
@Test
194+
public void givenWebClientBaseURLConfiguredToWireMock_whenMulitpleGet_thenWireMockReturnsMultipleResponsesBasedOnState() {
195+
// Stubbing response for the first call
196+
stubFor(WireMock.get(urlEqualTo("/weather?city=London"))
197+
.inScenario("Weather Scenario")
198+
.whenScenarioStateIs("started")
199+
.willReturn(aResponse().withStatus(200)
200+
.withHeader("Content-Type", "application/json")
201+
.withBody("{\"city\": \"London\", \"temperature\": 20, \"description\": \"Cloudy\"}"))
202+
.willSetStateTo("Weather Found"));
203+
204+
// Stubbing response for the second call
205+
stubFor(WireMock.get(urlEqualTo("/weather?city=London"))
206+
.inScenario("Weather Scenario")
207+
.whenScenarioStateIs("Weather Found")
208+
.willReturn(aResponse().withStatus(200)
209+
.withHeader("Content-Type", "application/json")
210+
.withBody("{\"city\": \"London\", \"temperature\": 25, \"description\": \"Sunny\"}")));
211+
212+
// Create WebClient instance with WireMock base URL
213+
WebClient webClient = webClientBuilder.baseUrl("http://localhost:" + wireMockPort)
214+
.build();
215+
216+
// Fetch weather data for London
217+
WeatherData firstWeatherData = webClient.get()
218+
.uri("/weather?city=London")
219+
.retrieve()
220+
.bodyToMono(WeatherData.class)
221+
.block();
222+
223+
// Assert the first response
224+
assertNotNull(firstWeatherData);
225+
assertEquals("London", firstWeatherData.getCity());
226+
assertEquals(20, firstWeatherData.getTemperature());
227+
assertEquals("Cloudy", firstWeatherData.getDescription());
228+
229+
// Fetch weather data for London again
230+
WeatherData secondWeatherData = webClient.get()
231+
.uri("/weather?city=London")
232+
.retrieve()
233+
.bodyToMono(WeatherData.class)
234+
.block();
235+
236+
// Assert the second response
237+
assertNotNull(secondWeatherData);
238+
assertEquals("London", secondWeatherData.getCity());
239+
assertEquals(25, secondWeatherData.getTemperature());
240+
assertEquals("Sunny", secondWeatherData.getDescription());
241+
}
242+
}

0 commit comments

Comments
 (0)