Skip to content

Commit 20de500

Browse files
committed
Hamcrest methods in WebTestClient
Issue: SPR-16729
1 parent 0c62d6b commit 20de500

File tree

11 files changed

+210
-55
lines changed

11 files changed

+210
-55
lines changed

spring-test/src/main/java/org/springframework/test/web/reactive/server/DefaultWebTestClient.java

Lines changed: 30 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818

1919
import java.net.URI;
2020
import java.nio.charset.Charset;
21+
import java.nio.charset.StandardCharsets;
2122
import java.time.Duration;
2223
import java.time.ZonedDateTime;
2324
import java.util.Arrays;
@@ -27,8 +28,9 @@
2728
import java.util.concurrent.atomic.AtomicLong;
2829
import java.util.function.Consumer;
2930
import java.util.function.Function;
30-
import javax.xml.xpath.XPathExpressionException;
3131

32+
import org.hamcrest.Matcher;
33+
import org.hamcrest.MatcherAssert;
3234
import org.reactivestreams.Publisher;
3335
import reactor.core.publisher.Flux;
3436

@@ -40,6 +42,7 @@
4042
import org.springframework.http.client.reactive.ClientHttpConnector;
4143
import org.springframework.http.client.reactive.ClientHttpRequest;
4244
import org.springframework.lang.Nullable;
45+
import org.springframework.test.util.AssertionErrors;
4346
import org.springframework.test.util.JsonExpectationsHelper;
4447
import org.springframework.test.util.XmlExpectationsHelper;
4548
import org.springframework.util.Assert;
@@ -50,10 +53,6 @@
5053
import org.springframework.web.reactive.function.client.WebClient;
5154
import org.springframework.web.util.UriBuilder;
5255

53-
import static java.nio.charset.StandardCharsets.UTF_8;
54-
import static org.springframework.test.util.AssertionErrors.assertEquals;
55-
import static org.springframework.test.util.AssertionErrors.assertTrue;
56-
5756
/**
5857
* Default implementation of {@link WebTestClient}.
5958
*
@@ -384,7 +383,22 @@ protected EntityExchangeResult<B> getResult() {
384383
@Override
385384
public <T extends S> T isEqualTo(B expected) {
386385
this.result.assertWithDiagnostics(() ->
387-
assertEquals("Response body", expected, this.result.getResponseBody()));
386+
AssertionErrors.assertEquals("Response body", expected, this.result.getResponseBody()));
387+
return self();
388+
}
389+
390+
@Override
391+
public <T extends S> T value(Matcher<B> matcher) {
392+
this.result.assertWithDiagnostics(() -> MatcherAssert.assertThat(this.result.getResponseBody(), matcher));
393+
return self();
394+
}
395+
396+
@Override
397+
public <T extends S, R> T value(Function<B, R> bodyMapper, Matcher<R> matcher) {
398+
this.result.assertWithDiagnostics(() -> {
399+
B body = this.result.getResponseBody();
400+
MatcherAssert.assertThat(bodyMapper.apply(body), matcher);
401+
});
388402
return self();
389403
}
390404

@@ -417,7 +431,8 @@ private static class DefaultListBodySpec<E> extends DefaultBodySpec<List<E>, Lis
417431
public ListBodySpec<E> hasSize(int size) {
418432
List<E> actual = getResult().getResponseBody();
419433
String message = "Response body does not contain " + size + " elements";
420-
getResult().assertWithDiagnostics(() -> assertEquals(message, size, (actual != null ? actual.size() : 0)));
434+
getResult().assertWithDiagnostics(() ->
435+
AssertionErrors.assertEquals(message, size, (actual != null ? actual.size() : 0)));
421436
return this;
422437
}
423438

@@ -427,7 +442,8 @@ public ListBodySpec<E> contains(E... elements) {
427442
List<E> expected = Arrays.asList(elements);
428443
List<E> actual = getResult().getResponseBody();
429444
String message = "Response body does not contain " + expected;
430-
getResult().assertWithDiagnostics(() -> assertTrue(message, (actual != null && actual.containsAll(expected))));
445+
getResult().assertWithDiagnostics(() ->
446+
AssertionErrors.assertTrue(message, (actual != null && actual.containsAll(expected))));
431447
return this;
432448
}
433449

@@ -437,7 +453,8 @@ public ListBodySpec<E> doesNotContain(E... elements) {
437453
List<E> expected = Arrays.asList(elements);
438454
List<E> actual = getResult().getResponseBody();
439455
String message = "Response body should not have contained " + expected;
440-
getResult().assertWithDiagnostics(() -> assertTrue(message, (actual == null || !actual.containsAll(expected))));
456+
getResult().assertWithDiagnostics(() ->
457+
AssertionErrors.assertTrue(message, (actual == null || !actual.containsAll(expected))));
441458
return this;
442459
}
443460

@@ -461,7 +478,8 @@ private static class DefaultBodyContentSpec implements BodyContentSpec {
461478

462479
@Override
463480
public EntityExchangeResult<Void> isEmpty() {
464-
this.result.assertWithDiagnostics(() -> assertTrue("Expected empty body", this.isEmpty));
481+
this.result.assertWithDiagnostics(() ->
482+
AssertionErrors.assertTrue("Expected empty body", this.isEmpty));
465483
return new EntityExchangeResult<>(this.result, null);
466484
}
467485

@@ -506,8 +524,8 @@ private String getBodyAsString() {
506524
if (body == null || body.length == 0) {
507525
return "";
508526
}
509-
MediaType mediaType = this.result.getResponseHeaders().getContentType();
510-
Charset charset = Optional.ofNullable(mediaType).map(MimeType::getCharset).orElse(UTF_8);
527+
Charset charset = Optional.ofNullable(this.result.getResponseHeaders().getContentType())
528+
.map(MimeType::getCharset).orElse(StandardCharsets.UTF_8);
511529
return new String(body, charset);
512530
}
513531

spring-test/src/main/java/org/springframework/test/web/reactive/server/HeaderAssertions.java

Lines changed: 34 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -17,17 +17,16 @@
1717
package org.springframework.test.web.reactive.server;
1818

1919
import java.util.Arrays;
20-
import java.util.regex.Pattern;
20+
21+
import org.hamcrest.Matcher;
22+
import org.hamcrest.MatcherAssert;
2123

2224
import org.springframework.http.CacheControl;
2325
import org.springframework.http.ContentDisposition;
2426
import org.springframework.http.HttpHeaders;
2527
import org.springframework.http.MediaType;
2628
import org.springframework.lang.Nullable;
27-
28-
import static org.springframework.test.util.AssertionErrors.assertEquals;
29-
import static org.springframework.test.util.AssertionErrors.assertTrue;
30-
import static org.springframework.test.util.AssertionErrors.fail;
29+
import org.springframework.test.util.AssertionErrors;
3130

3231
/**
3332
* Assertions on headers of the response.
@@ -59,20 +58,35 @@ public WebTestClient.ResponseSpec valueEquals(String headerName, String... value
5958
}
6059

6160
/**
62-
* Expect a header with the given name whose first value matches the
63-
* provided regex pattern.
61+
* Match the primary value of the response header with a regex.
6462
* @param name the header name
65-
* @param pattern the String pattern to pass to {@link Pattern#compile(String)}
63+
* @param pattern the regex pattern
6664
*/
6765
public WebTestClient.ResponseSpec valueMatches(String name, String pattern) {
66+
String value = getRequiredValue(name);
67+
String message = getMessage(name) + "=[" + value + "] does not match [" + pattern + "]";
68+
this.exchangeResult.assertWithDiagnostics(() -> AssertionErrors.assertTrue(message, value.matches(pattern)));
69+
return this.responseSpec;
70+
}
71+
72+
/**
73+
* Assert the primary value of the response header with a {@link Matcher}.
74+
* @param name the header name
75+
* @param matcher the matcher to sue
76+
* @since 5.1
77+
*/
78+
public WebTestClient.ResponseSpec value(String name, Matcher<? super String> matcher) {
79+
String value = getRequiredValue(name);
80+
this.exchangeResult.assertWithDiagnostics(() -> MatcherAssert.assertThat(value, matcher));
81+
return this.responseSpec;
82+
}
83+
84+
private String getRequiredValue(String name) {
6885
String value = getHeaders().getFirst(name);
6986
if (value == null) {
70-
fail(getMessage(name) + " not found");
87+
AssertionErrors.fail(getMessage(name) + " not found");
7188
}
72-
boolean match = Pattern.compile(pattern).matcher(value).matches();
73-
String message = getMessage(name) + "=[" + value + "] does not match [" + pattern + "]";
74-
this.exchangeResult.assertWithDiagnostics(() -> assertTrue(message, match));
75-
return this.responseSpec;
89+
return value;
7690
}
7791

7892
/**
@@ -82,7 +96,7 @@ public WebTestClient.ResponseSpec valueMatches(String name, String pattern) {
8296
public WebTestClient.ResponseSpec exists(String name) {
8397
if (!getHeaders().containsKey(name)) {
8498
String message = getMessage(name) + " does not exist";
85-
this.exchangeResult.assertWithDiagnostics(() -> fail(message));
99+
this.exchangeResult.assertWithDiagnostics(() -> AssertionErrors.fail(message));
86100
}
87101
return this.responseSpec;
88102
}
@@ -93,7 +107,7 @@ public WebTestClient.ResponseSpec exists(String name) {
93107
public WebTestClient.ResponseSpec doesNotExist(String name) {
94108
if (getHeaders().containsKey(name)) {
95109
String message = getMessage(name) + " exists with value=[" + getHeaders().getFirst(name) + "]";
96-
this.exchangeResult.assertWithDiagnostics(() -> fail(message));
110+
this.exchangeResult.assertWithDiagnostics(() -> AssertionErrors.fail(message));
97111
}
98112
return this.responseSpec;
99113
}
@@ -140,7 +154,7 @@ public WebTestClient.ResponseSpec contentTypeCompatibleWith(MediaType mediaType)
140154
MediaType actual = getHeaders().getContentType();
141155
String message = getMessage("Content-Type") + "=[" + actual + "] is not compatible with [" + mediaType + "]";
142156
this.exchangeResult.assertWithDiagnostics(() ->
143-
assertTrue(message, (actual != null && actual.isCompatibleWith(mediaType))));
157+
AssertionErrors.assertTrue(message, (actual != null && actual.isCompatibleWith(mediaType))));
144158
return this.responseSpec;
145159
}
146160

@@ -175,7 +189,10 @@ private String getMessage(String headerName) {
175189
}
176190

177191
private WebTestClient.ResponseSpec assertHeader(String name, @Nullable Object expected, @Nullable Object actual) {
178-
this.exchangeResult.assertWithDiagnostics(() -> assertEquals(getMessage(name), expected, actual));
192+
this.exchangeResult.assertWithDiagnostics(() -> {
193+
String message = getMessage(name);
194+
AssertionErrors.assertEquals(message, expected, actual);
195+
});
179196
return this.responseSpec;
180197
}
181198

spring-test/src/main/java/org/springframework/test/web/reactive/server/JsonPathAssertions.java

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616

1717
package org.springframework.test.web.reactive.server;
1818

19+
import org.hamcrest.Matcher;
20+
1921
import org.springframework.test.util.JsonPathExpectationsHelper;
2022

2123
/**
@@ -132,4 +134,22 @@ public WebTestClient.BodyContentSpec isMap() {
132134
return this.bodySpec;
133135
}
134136

137+
/**
138+
* Delegates to {@link JsonPathExpectationsHelper#assertValue(String, Matcher)}.
139+
* @since 5.1
140+
*/
141+
public <T> WebTestClient.BodyContentSpec value(Matcher<T> matcher) {
142+
this.pathHelper.assertValue(this.content, matcher);
143+
return this.bodySpec;
144+
}
145+
146+
/**
147+
* Delegates to {@link JsonPathExpectationsHelper#assertValue(String, Matcher, Class)}.
148+
* @since 5.1
149+
*/
150+
public <T> WebTestClient.BodyContentSpec value(Matcher<T> matcher, Class<T> targetType) {
151+
this.pathHelper.assertValue(this.content, matcher, targetType);
152+
return this.bodySpec;
153+
}
154+
135155
}

spring-test/src/main/java/org/springframework/test/web/reactive/server/StatusAssertions.java

Lines changed: 22 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,11 @@
1616

1717
package org.springframework.test.web.reactive.server;
1818

19-
import org.springframework.http.HttpStatus;
19+
import org.hamcrest.Matcher;
20+
import org.hamcrest.MatcherAssert;
2021

21-
import static org.springframework.test.util.AssertionErrors.assertEquals;
22+
import org.springframework.http.HttpStatus;
23+
import org.springframework.test.util.AssertionErrors;
2224

2325
/**
2426
* Assertions on the response status.
@@ -52,7 +54,7 @@ public WebTestClient.ResponseSpec isEqualTo(HttpStatus status) {
5254
*/
5355
public WebTestClient.ResponseSpec isEqualTo(int status) {
5456
int actual = this.exchangeResult.getStatus().value();
55-
this.exchangeResult.assertWithDiagnostics(() -> assertEquals("Status", status, actual));
57+
this.exchangeResult.assertWithDiagnostics(() -> AssertionErrors.assertEquals("Status", status, actual));
5658
return this.responseSpec;
5759
}
5860

@@ -155,7 +157,7 @@ public WebTestClient.ResponseSpec isNotFound() {
155157
public WebTestClient.ResponseSpec reasonEquals(String reason) {
156158
String actual = this.exchangeResult.getStatus().getReasonPhrase();
157159
String message = "Response status reason";
158-
this.exchangeResult.assertWithDiagnostics(() -> assertEquals(message, reason, actual));
160+
this.exchangeResult.assertWithDiagnostics(() -> AssertionErrors.assertEquals(message, reason, actual));
159161
return this.responseSpec;
160162
}
161163

@@ -195,17 +197,30 @@ public WebTestClient.ResponseSpec is5xxServerError() {
195197
return assertSeriesAndReturn(expected);
196198
}
197199

200+
/**
201+
* Match the response status value with a Hamcrest matcher.
202+
* @param matcher the matcher to use
203+
* @since 5.1
204+
*/
205+
public WebTestClient.ResponseSpec value(Matcher<Integer> matcher) {
206+
int value = this.exchangeResult.getStatus().value();
207+
this.exchangeResult.assertWithDiagnostics(() -> MatcherAssert.assertThat("Response status", value, matcher));
208+
return this.responseSpec;
209+
}
210+
198211

199212
private WebTestClient.ResponseSpec assertStatusAndReturn(HttpStatus expected) {
200213
HttpStatus actual = this.exchangeResult.getStatus();
201-
this.exchangeResult.assertWithDiagnostics(() -> assertEquals("Status", expected, actual));
214+
this.exchangeResult.assertWithDiagnostics(() -> AssertionErrors.assertEquals("Status", expected, actual));
202215
return this.responseSpec;
203216
}
204217

205218
private WebTestClient.ResponseSpec assertSeriesAndReturn(HttpStatus.Series expected) {
206219
HttpStatus status = this.exchangeResult.getStatus();
207-
this.exchangeResult.assertWithDiagnostics(() ->
208-
assertEquals("Range for response status value " + status, expected, status.series()));
220+
this.exchangeResult.assertWithDiagnostics(() -> {
221+
String message = "Range for response status value " + status;
222+
AssertionErrors.assertEquals(message, expected, status.series());
223+
});
209224
return this.responseSpec;
210225
}
211226

spring-test/src/main/java/org/springframework/test/web/reactive/server/WebTestClient.java

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
import java.util.function.Consumer;
2626
import java.util.function.Function;
2727

28+
import org.hamcrest.Matcher;
2829
import org.reactivestreams.Publisher;
2930

3031
import org.springframework.context.ApplicationContext;
@@ -758,6 +759,19 @@ interface BodySpec<B, S extends BodySpec<B, S>> {
758759
*/
759760
<T extends S> T isEqualTo(B expected);
760761

762+
/**
763+
* Assert the extracted body with a {@link Matcher}.
764+
* @since 5.1
765+
*/
766+
<T extends S> T value(Matcher<B> matcher);
767+
768+
/**
769+
* Transform the extracted the body with a function, e.g. extracting a
770+
* property, and assert the mapped value with a {@link Matcher}.
771+
* @since 5.1
772+
*/
773+
<T extends S, R> T value(Function<B, R> bodyMapper, Matcher<R> matcher);
774+
761775
/**
762776
* Assert the exchange result with the given {@link Consumer}.
763777
*/
@@ -851,8 +865,8 @@ interface BodyContentSpec {
851865
* formatting specifiers as defined in {@link String#format}.
852866
* @param expression the XPath expression
853867
* @param args arguments to parameterize the expression
854-
* @see #xpath(String, Map, Object...)
855868
* @since 5.1
869+
* @see #xpath(String, Map, Object...)
856870
*/
857871
default XpathAssertions xpath(String expression, Object... args){
858872
return xpath(expression, null, args);

0 commit comments

Comments
 (0)