Skip to content

Commit 1e8c7e5

Browse files
committed
WebTestClient assert response body with Consumer<B>
Issue: SPR-15421
1 parent 0e84f24 commit 1e8c7e5

File tree

6 files changed

+134
-20
lines changed

6 files changed

+134
-20
lines changed

spring-core/src/main/java/org/springframework/core/codec/ResourceDecoder.java

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2016 the original author or authors.
2+
* Copyright 2002-2017 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -57,6 +57,13 @@ public boolean canDecode(ResolvableType elementType, MimeType mimeType) {
5757
public Flux<Resource> decode(Publisher<DataBuffer> inputStream, ResolvableType elementType,
5858
MimeType mimeType, Map<String, Object> hints) {
5959

60+
return Flux.from(decodeToMono(inputStream, elementType, mimeType, hints));
61+
}
62+
63+
@Override
64+
public Mono<Resource> decodeToMono(Publisher<DataBuffer> inputStream, ResolvableType elementType,
65+
MimeType mimeType, Map<String, Object> hints) {
66+
6067
Class<?> clazz = elementType.getRawClass();
6168

6269
Mono<byte[]> byteArray = Flux.from(inputStream).
@@ -70,13 +77,13 @@ public Flux<Resource> decode(Publisher<DataBuffer> inputStream, ResolvableType e
7077

7178

7279
if (InputStreamResource.class.equals(clazz)) {
73-
return Flux.from(byteArray.map(ByteArrayInputStream::new).map(InputStreamResource::new));
80+
return Mono.from(byteArray.map(ByteArrayInputStream::new).map(InputStreamResource::new));
7481
}
7582
else if (clazz.isAssignableFrom(ByteArrayResource.class)) {
76-
return Flux.from(byteArray.map(ByteArrayResource::new));
83+
return Mono.from(byteArray.map(ByteArrayResource::new));
7784
}
7885
else {
79-
return Flux.error(new IllegalStateException("Unsupported resource class: " + clazz));
86+
return Mono.error(new IllegalStateException("Unsupported resource class: " + clazz));
8087
}
8188
}
8289

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

Lines changed: 57 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,9 @@
2323
import java.util.Arrays;
2424
import java.util.List;
2525
import java.util.Map;
26+
import java.util.Optional;
2627
import java.util.concurrent.atomic.AtomicLong;
28+
import java.util.function.Consumer;
2729
import java.util.function.Function;
2830
import java.util.function.UnaryOperator;
2931

@@ -32,13 +34,14 @@
3234
import reactor.core.publisher.Mono;
3335

3436
import org.springframework.core.ResolvableType;
35-
import org.springframework.core.io.buffer.DataBuffer;
37+
import org.springframework.core.io.ByteArrayResource;
3638
import org.springframework.http.HttpHeaders;
3739
import org.springframework.http.HttpMethod;
3840
import org.springframework.http.MediaType;
3941
import org.springframework.http.client.reactive.ClientHttpConnector;
4042
import org.springframework.http.client.reactive.ClientHttpRequest;
4143
import org.springframework.util.Assert;
44+
import org.springframework.util.MimeType;
4245
import org.springframework.util.MultiValueMap;
4346
import org.springframework.web.reactive.function.BodyInserter;
4447
import org.springframework.web.reactive.function.client.ClientResponse;
@@ -47,9 +50,9 @@
4750
import org.springframework.web.server.ServerWebExchange;
4851
import org.springframework.web.util.UriBuilder;
4952

53+
import static java.nio.charset.StandardCharsets.UTF_8;
5054
import static org.springframework.test.util.AssertionErrors.assertEquals;
5155
import static org.springframework.test.util.AssertionErrors.assertTrue;
52-
import static org.springframework.web.reactive.function.BodyExtractors.toDataBuffers;
5356
import static org.springframework.web.reactive.function.BodyExtractors.toFlux;
5457
import static org.springframework.web.reactive.function.BodyExtractors.toMono;
5558

@@ -292,6 +295,7 @@ private DefaultResponseSpec toResponseSpec(Mono<ClientResponse> mono) {
292295
ExchangeResult exchangeResult = wiretapConnector.claimRequest(this.requestId);
293296
return new DefaultResponseSpec(exchangeResult, clientResponse, getTimeout());
294297
}
298+
295299
}
296300

297301

@@ -326,11 +330,12 @@ public <T> FluxExchangeResult<T> decodeToFlux(ResolvableType elementType) {
326330
return new FluxExchangeResult<>(this, body, this.timeout);
327331
}
328332

329-
public EntityExchangeResult<Void> decodeToEmpty() {
330-
DataBuffer buffer = this.response.body(toDataBuffers()).blockFirst(this.timeout);
331-
assertWithDiagnostics(() -> assertTrue("Expected empty body", buffer == null));
332-
return new EntityExchangeResult<>(this, null);
333+
public EntityExchangeResult<byte[]> decodeToByteArray() {
334+
ByteArrayResource resource = this.response.body(toMono(ByteArrayResource.class)).block(this.timeout);
335+
byte[] body = (resource != null ? resource.getByteArray() : null);
336+
return new EntityExchangeResult<>(this, body);
333337
}
338+
334339
}
335340

336341

@@ -375,7 +380,7 @@ public <E> ListBodySpec<E> expectBodyList(ResolvableType elementType) {
375380

376381
@Override
377382
public BodyContentSpec expectBody() {
378-
return new DefaultBodyContentSpec(this.result);
383+
return new DefaultBodyContentSpec(this.result.decodeToByteArray());
379384
}
380385

381386
@Override
@@ -387,6 +392,7 @@ public <T> FluxExchangeResult<T> returnResult(Class<T> elementType) {
387392
public <T> FluxExchangeResult<T> returnResult(ResolvableType elementType) {
388393
return this.result.decodeToFlux(elementType);
389394
}
395+
390396
}
391397

392398

@@ -406,11 +412,18 @@ protected EntityExchangeResult<B> getResult() {
406412

407413
@Override
408414
public <T extends S> T isEqualTo(B expected) {
409-
Object actual = this.result.getResponseBody();
415+
B actual = this.result.getResponseBody();
410416
this.result.assertWithDiagnostics(() -> assertEquals("Response body", expected, actual));
411417
return self();
412418
}
413419

420+
@Override
421+
public <T extends S> T consumeWith(Consumer<B> consumer) {
422+
B actual = this.result.getResponseBody();
423+
this.result.assertWithDiagnostics(() -> consumer.accept(actual));
424+
return self();
425+
}
426+
414427
@SuppressWarnings("unchecked")
415428
private <T extends S> T self() {
416429
return (T) this;
@@ -420,6 +433,7 @@ private <T extends S> T self() {
420433
public EntityExchangeResult<B> returnResult() {
421434
return this.result;
422435
}
436+
423437
}
424438

425439

@@ -465,23 +479,55 @@ public ListBodySpec<E> doesNotContain(E... elements) {
465479
public EntityExchangeResult<List<E>> returnResult() {
466480
return getResult();
467481
}
482+
468483
}
469484

470485

471486
private static class DefaultBodyContentSpec implements BodyContentSpec {
472487

473-
private final UndecodedExchangeResult result;
488+
private final EntityExchangeResult<byte[]> result;
474489

490+
private final boolean isEmpty;
475491

476-
DefaultBodyContentSpec(UndecodedExchangeResult result) {
492+
493+
DefaultBodyContentSpec(EntityExchangeResult<byte[]> result) {
477494
this.result = result;
495+
this.isEmpty = (result.getResponseBody() == null);
478496
}
479497

480498

481499
@Override
482500
public EntityExchangeResult<Void> isEmpty() {
483-
return this.result.decodeToEmpty();
501+
this.result.assertWithDiagnostics(() -> assertTrue("Expected empty body", this.isEmpty));
502+
return new EntityExchangeResult<>(this.result, null);
484503
}
504+
505+
@Override
506+
public BodyContentSpec consumeAsStringWith(Consumer<String> consumer) {
507+
this.result.assertWithDiagnostics(() -> consumer.accept(getBodyAsString()));
508+
return this;
509+
}
510+
511+
private String getBodyAsString() {
512+
if (this.isEmpty) {
513+
return null;
514+
}
515+
MediaType mediaType = this.result.getResponseHeaders().getContentType();
516+
Charset charset = Optional.ofNullable(mediaType).map(MimeType::getCharset).orElse(UTF_8);
517+
return new String(this.result.getResponseBody(), charset);
518+
}
519+
520+
@Override
521+
public BodyContentSpec consumeWith(Consumer<byte[]> consumer) {
522+
this.result.assertWithDiagnostics(() -> consumer.accept(this.result.getResponseBody()));
523+
return this;
524+
}
525+
526+
@Override
527+
public EntityExchangeResult<byte[]> returnResult() {
528+
return this.result;
529+
}
530+
485531
}
486532

487533
}

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

Lines changed: 32 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -486,7 +486,7 @@ interface RequestBodySpec extends RequestHeadersSpec<RequestBodySpec> {
486486
}
487487

488488
/**
489-
* Specification for processing the response and applying expectations.
489+
* Spec for declaring expectations on the response.
490490
*/
491491
interface ResponseSpec {
492492

@@ -544,7 +544,7 @@ interface ResponseSpec {
544544
}
545545

546546
/**
547-
* Specification for asserting a response body decoded to a single Object.
547+
* Spec for expectations on the response body decoded to a single Object.
548548
*/
549549
interface BodySpec<B, S extends BodySpec<B, S>> {
550550

@@ -553,6 +553,11 @@ interface BodySpec<B, S extends BodySpec<B, S>> {
553553
*/
554554
<T extends S> T isEqualTo(B expected);
555555

556+
/**
557+
* Assert the extracted body with the given {@link Consumer}.
558+
*/
559+
<T extends S> T consumeWith(Consumer<B> consumer);
560+
556561
/**
557562
* Return the exchange result with the decoded body.
558563
*/
@@ -561,7 +566,7 @@ interface BodySpec<B, S extends BodySpec<B, S>> {
561566
}
562567

563568
/**
564-
* Specification for asserting a response body decoded to a List.
569+
* Spec for expectations on the response body decoded to a List.
565570
*/
566571
interface ListBodySpec<E> extends BodySpec<List<E>, ListBodySpec<E>> {
567572

@@ -587,6 +592,9 @@ interface ListBodySpec<E> extends BodySpec<List<E>, ListBodySpec<E>> {
587592

588593
}
589594

595+
/**
596+
* Spec for expectations on the response body content.
597+
*/
590598
interface BodyContentSpec {
591599

592600
/**
@@ -595,6 +603,27 @@ interface BodyContentSpec {
595603
*/
596604
EntityExchangeResult<Void> isEmpty();
597605

606+
/**
607+
* Assert the response body content converted to a String with the given
608+
* {@link Consumer}. The String is created with the {@link Charset} from
609+
* the "content-type" response header or {@code UTF-8} otherwise.
610+
* @param consumer the consumer for the response body; the input String
611+
* may be {@code null} if there was no response body.
612+
*/
613+
BodyContentSpec consumeAsStringWith(Consumer<String> consumer);
614+
615+
/**
616+
* Assert the response body content with the given {@link Consumer}.
617+
* @param consumer the consumer for the response body; the input
618+
* {@code byte[]} may be {@code null} if there was no response body.
619+
*/
620+
BodyContentSpec consumeWith(Consumer<byte[]> consumer);
621+
622+
/**
623+
* Return the exchange result with body content as {@code byte[]}.
624+
*/
625+
EntityExchangeResult<byte[]> returnResult();
626+
598627
}
599628

600629
}

spring-test/src/test/java/org/springframework/test/web/reactive/server/samples/ResponseEntityTests.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,10 +42,12 @@
4242

4343
import static java.time.Duration.ofMillis;
4444
import static org.hamcrest.CoreMatchers.endsWith;
45+
import static org.junit.Assert.assertEquals;
4546
import static org.junit.Assert.assertThat;
4647
import static org.springframework.core.ResolvableType.forClassWithGenerics;
4748
import static org.springframework.http.MediaType.TEXT_EVENT_STREAM;
4849

50+
4951
/**
5052
* Annotated controllers accepting and returning typed Objects.
5153
*
@@ -66,6 +68,15 @@ public void entity() throws Exception {
6668
.expectBody(Person.class).isEqualTo(new Person("John"));
6769
}
6870

71+
@Test
72+
public void entityWithConsumer() throws Exception {
73+
this.client.get().uri("/persons/John")
74+
.exchange()
75+
.expectStatus().isOk()
76+
.expectHeader().contentType(MediaType.APPLICATION_JSON_UTF8)
77+
.expectBody(Person.class).consumeWith(p -> assertEquals(new Person("John"), p));
78+
}
79+
6980
@Test
7081
public void entityList() throws Exception {
7182

spring-test/src/test/java/org/springframework/test/web/reactive/server/samples/bind/ApplicationContextTests.java

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@
3434
import org.springframework.web.server.ServerWebExchange;
3535
import org.springframework.web.server.WebFilter;
3636

37+
import static org.junit.Assert.assertEquals;
38+
3739
/**
3840
* Binding to server infrastructure declared in a Spring ApplicationContext.
3941
*
@@ -58,14 +60,23 @@ public void setUp() throws Exception {
5860
.build();
5961
}
6062

63+
6164
@Test
62-
public void basic() throws Exception {
65+
public void bodyContent() throws Exception {
6366
this.client.get().uri("/principal")
6467
.exchange()
6568
.expectStatus().isOk()
6669
.expectBody(String.class).isEqualTo("Hello Mr. Pablo!");
6770
}
6871

72+
@Test
73+
public void bodyContentWithConsumer() throws Exception {
74+
this.client.get().uri("/principal")
75+
.exchange()
76+
.expectStatus().isOk()
77+
.expectBody().consumeAsStringWith(body -> assertEquals("Hello Mr. Pablo!", body));
78+
}
79+
6980
@Test
7081
public void perRequestExchangeMutator() throws Exception {
7182
this.client.exchangeMutator(principal("Giovanni"))

spring-test/src/test/java/org/springframework/test/web/reactive/server/samples/bind/ControllerTests.java

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@
2929
import org.springframework.web.server.ServerWebExchange;
3030
import org.springframework.web.server.WebFilter;
3131

32+
import static org.junit.Assert.assertEquals;
33+
3234
/**
3335
* Bind to annotated controllers.
3436
*
@@ -44,13 +46,21 @@ public class ControllerTests {
4446

4547

4648
@Test
47-
public void basic() throws Exception {
49+
public void bodyContent() throws Exception {
4850
this.client.get().uri("/principal")
4951
.exchange()
5052
.expectStatus().isOk()
5153
.expectBody(String.class).isEqualTo("Hello Mr. Pablo!");
5254
}
5355

56+
@Test
57+
public void bodyContentWithConsumer() throws Exception {
58+
this.client.get().uri("/principal")
59+
.exchange()
60+
.expectStatus().isOk()
61+
.expectBody().consumeAsStringWith(body -> assertEquals("Hello Mr. Pablo!", body));
62+
}
63+
5464
@Test
5565
public void perRequestExchangeMutator() throws Exception {
5666
this.client.exchangeMutator(principal("Giovanni"))

0 commit comments

Comments
 (0)