Improve writing in mock reactive request and response
Before this change the write Publisher was saved and Mono.empty()
returned from the write metohd which did not properly implement
the write contract since no writing ("consuming") was done.
This can be a problem in some cases. For example the request may appear
to succeed even if the publisher produces an error later when
subscribed to later after request handling completes.
This commit introduces a writeHandler function in the mock request and
response. By default it "writes" by consuming the content immediately,
which allows it to return a Mono<Void> that properly reflects when
writing is done, and it also caches the data so it may be replayed
later for test assertions.
For streaming scenario a custom writeHandler may be registered which
allows the custom handling to determine how long to stream before
cancelling so request handling may complete.
Issue: SPR-14590
This commit is contained in:
@@ -146,16 +146,14 @@ public class ResponseEntityResultHandler extends AbstractMessageWriterResultHand
|
||||
.forEach(entry -> responseHeaders.put(entry.getKey(), entry.getValue()));
|
||||
}
|
||||
if(httpEntity.getBody() == null) {
|
||||
exchange.getResponse().setComplete();
|
||||
return Mono.empty();
|
||||
return exchange.getResponse().setComplete();
|
||||
}
|
||||
|
||||
String etag = entityHeaders.getETag();
|
||||
Instant lastModified = Instant.ofEpochMilli(entityHeaders.getLastModified());
|
||||
HttpMethod httpMethod = exchange.getRequest().getMethod();
|
||||
if (SAFE_METHODS.contains(httpMethod) && exchange.checkNotModified(etag, lastModified)) {
|
||||
exchange.getResponse().setComplete();
|
||||
return Mono.empty();
|
||||
return exchange.getResponse().setComplete();
|
||||
}
|
||||
|
||||
return writeBody(httpEntity.getBody(), bodyType, exchange);
|
||||
|
||||
@@ -23,6 +23,7 @@ import java.util.List;
|
||||
|
||||
import org.junit.Test;
|
||||
import reactor.core.publisher.Mono;
|
||||
import reactor.test.StepVerifier;
|
||||
|
||||
import org.springframework.core.codec.CharSequenceEncoder;
|
||||
import org.springframework.core.io.buffer.DataBuffer;
|
||||
@@ -89,7 +90,7 @@ public class DefaultClientRequestBuilderTests {
|
||||
|
||||
assertEquals("MyValue", request.getHeaders().getFirst("MyKey"));
|
||||
assertEquals("bar", request.getCookies().getFirst("foo").getValue());
|
||||
assertNull(request.getBody());
|
||||
StepVerifier.create(request.getBody()).expectComplete().verify();
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -36,7 +36,6 @@ import org.springframework.mock.http.server.reactive.test.MockServerHttpResponse
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNull;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
@@ -245,8 +244,7 @@ public class DefaultServerResponseBuilderTests {
|
||||
|
||||
assertEquals(HttpStatus.CREATED, response.getStatusCode());
|
||||
assertEquals("MyValue", response.getHeaders().getFirst("MyKey"));
|
||||
assertNull(response.getBody());
|
||||
|
||||
StepVerifier.create(response.getBody()).expectComplete().verify();
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -261,7 +259,7 @@ public class DefaultServerResponseBuilderTests {
|
||||
|
||||
result.then(res -> res.writeTo(exchange, strategies)).block();
|
||||
|
||||
assertNull(response.getBody());
|
||||
StepVerifier.create(response.getBody()).expectComplete().verify();
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
@@ -37,7 +37,6 @@ import org.springframework.web.server.session.MockWebSessionManager;
|
||||
|
||||
import static org.junit.Assert.assertArrayEquals;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNull;
|
||||
|
||||
/**
|
||||
* @author Arjen Poutsma
|
||||
@@ -150,7 +149,7 @@ TODO: enable when ServerEntityResponse is reintroduced
|
||||
assertEquals(EnumSet.of(HttpMethod.GET, HttpMethod.HEAD, HttpMethod.OPTIONS),
|
||||
mockResponse.getHeaders().getAllow());
|
||||
|
||||
assertNull(mockResponse.getBody());
|
||||
StepVerifier.create(mockResponse.getBody()).expectComplete().verify();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -125,7 +125,10 @@ public class ResourceWebHandlerTests {
|
||||
assertEquals(headers.getLastModified() / 1000, resourceLastModifiedDate("test/foo.css") / 1000);
|
||||
assertEquals("bytes", headers.getFirst("Accept-Ranges"));
|
||||
assertEquals(1, headers.get("Accept-Ranges").size());
|
||||
assertNull(this.response.getBody());
|
||||
|
||||
StepVerifier.create(this.response.getBody())
|
||||
.expectErrorMatches(ex -> ex.getMessage().startsWith("The body is not set."))
|
||||
.verify();
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -123,7 +123,8 @@ public class MessageWriterResultHandlerTests {
|
||||
this.resultHandler.writeBody(body, returnType(type), this.exchange).block(Duration.ofSeconds(5));
|
||||
|
||||
assertNull(this.response.getHeaders().get("Content-Type"));
|
||||
assertNull(this.response.getBody());
|
||||
StepVerifier.create(this.response.getBody())
|
||||
.expectErrorMatches(ex -> ex.getMessage().startsWith("The body is not set.")).verify();
|
||||
}
|
||||
|
||||
@Test // SPR-13135
|
||||
|
||||
@@ -62,7 +62,6 @@ import org.springframework.web.server.adapter.DefaultServerWebExchange;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertNull;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.springframework.core.ResolvableType.forClassWithGenerics;
|
||||
import static org.springframework.http.ResponseEntity.notFound;
|
||||
@@ -156,7 +155,7 @@ public class ResponseEntityResultHandlerTests {
|
||||
|
||||
assertEquals(HttpStatus.NO_CONTENT, this.response.getStatusCode());
|
||||
assertEquals(0, this.response.getHeaders().size());
|
||||
assertNull(this.response.getBody());
|
||||
assertResponseBodyIsEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -170,7 +169,7 @@ public class ResponseEntityResultHandlerTests {
|
||||
assertEquals(HttpStatus.CREATED, this.response.getStatusCode());
|
||||
assertEquals(1, this.response.getHeaders().size());
|
||||
assertEquals(location, this.response.getHeaders().getLocation());
|
||||
assertNull(this.response.getBody());
|
||||
assertResponseBodyIsEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -180,7 +179,7 @@ public class ResponseEntityResultHandlerTests {
|
||||
HandlerResult result = handlerResult(returnValue, returnType);
|
||||
this.resultHandler.handleResult(createExchange(), result).block(Duration.ofSeconds(5));
|
||||
assertEquals(HttpStatus.NOT_FOUND, this.response.getStatusCode());
|
||||
assertNull(this.response.getBody());
|
||||
assertResponseBodyIsEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -311,7 +310,7 @@ public class ResponseEntityResultHandlerTests {
|
||||
this.resultHandler.handleResult(exchange, result).block(Duration.ofSeconds(5));
|
||||
|
||||
assertEquals(HttpStatus.NOT_FOUND, this.response.getStatusCode());
|
||||
assertNull(this.response.getBody());
|
||||
assertResponseBodyIsEmpty();
|
||||
}
|
||||
|
||||
|
||||
@@ -348,13 +347,19 @@ public class ResponseEntityResultHandlerTests {
|
||||
.verify();
|
||||
}
|
||||
|
||||
private void assertConditionalResponse(HttpStatus status, String body, String etag, Instant lastModified) throws Exception {
|
||||
private void assertResponseBodyIsEmpty() {
|
||||
StepVerifier.create(this.response.getBody()).expectComplete().verify();
|
||||
}
|
||||
|
||||
private void assertConditionalResponse(HttpStatus status, String body, String etag, Instant lastModified)
|
||||
throws Exception {
|
||||
|
||||
assertEquals(status, this.response.getStatusCode());
|
||||
if (body != null) {
|
||||
assertResponseBody(body);
|
||||
}
|
||||
else {
|
||||
assertNull(this.response.getBody());
|
||||
assertResponseBodyIsEmpty();
|
||||
}
|
||||
if (etag != null) {
|
||||
assertEquals(1, this.response.getHeaders().get(HttpHeaders.ETAG).size());
|
||||
|
||||
Reference in New Issue
Block a user