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:
Rossen Stoyanchev
2017-02-07 21:57:38 -05:00
parent d41d28f8ce
commit f2967467e0
12 changed files with 225 additions and 74 deletions

View File

@@ -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);

View File

@@ -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

View File

@@ -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();
}
/*

View File

@@ -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();
}
}

View File

@@ -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

View File

@@ -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

View File

@@ -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());