parent
f7678cdcdd
commit
4982b5fcb9
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2002-2020 the original author or authors.
|
||||
* Copyright 2002-2021 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
|
@ -32,6 +32,7 @@ import org.springframework.test.web.servlet.client.MockMvcWebTestClient;
|
|||
import org.springframework.util.concurrent.ListenableFuture;
|
||||
import org.springframework.util.concurrent.ListenableFutureTask;
|
||||
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.ResponseStatus;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
|
@ -99,7 +100,7 @@ public class AsyncTests {
|
|||
}
|
||||
|
||||
@Test
|
||||
public void deferredResultWithImmediateValue() throws Exception {
|
||||
public void deferredResultWithImmediateValue() {
|
||||
this.testClient.get()
|
||||
.uri("/1?deferredResultWithImmediateValue=true")
|
||||
.exchange()
|
||||
|
|
@ -109,7 +110,7 @@ public class AsyncTests {
|
|||
}
|
||||
|
||||
@Test
|
||||
public void deferredResultWithDelayedError() throws Exception {
|
||||
public void deferredResultWithDelayedError() {
|
||||
this.testClient.get()
|
||||
.uri("/1?deferredResultWithDelayedError=true")
|
||||
.exchange()
|
||||
|
|
@ -118,7 +119,7 @@ public class AsyncTests {
|
|||
}
|
||||
|
||||
@Test
|
||||
public void listenableFuture() throws Exception {
|
||||
public void listenableFuture() {
|
||||
this.testClient.get()
|
||||
.uri("/1?listenableFuture=true")
|
||||
.exchange()
|
||||
|
|
@ -142,17 +143,17 @@ public class AsyncTests {
|
|||
@RequestMapping(path = "/{id}", produces = "application/json")
|
||||
private static class AsyncController {
|
||||
|
||||
@RequestMapping(params = "callable")
|
||||
@GetMapping(params = "callable")
|
||||
public Callable<Person> getCallable() {
|
||||
return () -> new Person("Joe");
|
||||
}
|
||||
|
||||
@RequestMapping(params = "streaming")
|
||||
@GetMapping(params = "streaming")
|
||||
public StreamingResponseBody getStreaming() {
|
||||
return os -> os.write("name=Joe".getBytes(StandardCharsets.UTF_8));
|
||||
}
|
||||
|
||||
@RequestMapping(params = "streamingSlow")
|
||||
@GetMapping(params = "streamingSlow")
|
||||
public StreamingResponseBody getStreamingSlow() {
|
||||
return os -> {
|
||||
os.write("name=Joe".getBytes());
|
||||
|
|
@ -166,41 +167,41 @@ public class AsyncTests {
|
|||
};
|
||||
}
|
||||
|
||||
@RequestMapping(params = "streamingJson")
|
||||
@GetMapping(params = "streamingJson")
|
||||
public ResponseEntity<StreamingResponseBody> getStreamingJson() {
|
||||
return ResponseEntity.ok().contentType(MediaType.APPLICATION_JSON)
|
||||
.body(os -> os.write("{\"name\":\"Joe\",\"someDouble\":0.5}".getBytes(StandardCharsets.UTF_8)));
|
||||
}
|
||||
|
||||
@RequestMapping(params = "deferredResult")
|
||||
@GetMapping(params = "deferredResult")
|
||||
public DeferredResult<Person> getDeferredResult() {
|
||||
DeferredResult<Person> result = new DeferredResult<>();
|
||||
delay(100, () -> result.setResult(new Person("Joe")));
|
||||
return result;
|
||||
}
|
||||
|
||||
@RequestMapping(params = "deferredResultWithImmediateValue")
|
||||
@GetMapping(params = "deferredResultWithImmediateValue")
|
||||
public DeferredResult<Person> getDeferredResultWithImmediateValue() {
|
||||
DeferredResult<Person> result = new DeferredResult<>();
|
||||
result.setResult(new Person("Joe"));
|
||||
return result;
|
||||
}
|
||||
|
||||
@RequestMapping(params = "deferredResultWithDelayedError")
|
||||
@GetMapping(params = "deferredResultWithDelayedError")
|
||||
public DeferredResult<Person> getDeferredResultWithDelayedError() {
|
||||
DeferredResult<Person> result = new DeferredResult<>();
|
||||
delay(100, () -> result.setErrorResult(new RuntimeException("Delayed Error")));
|
||||
return result;
|
||||
}
|
||||
|
||||
@RequestMapping(params = "listenableFuture")
|
||||
@GetMapping(params = "listenableFuture")
|
||||
public ListenableFuture<Person> getListenableFuture() {
|
||||
ListenableFutureTask<Person> futureTask = new ListenableFutureTask<>(() -> new Person("Joe"));
|
||||
delay(100, futureTask);
|
||||
return futureTask;
|
||||
}
|
||||
|
||||
@RequestMapping(params = "completableFutureWithImmediateValue")
|
||||
@GetMapping(params = "completableFutureWithImmediateValue")
|
||||
public CompletableFuture<Person> getCompletableFutureWithImmediateValue() {
|
||||
CompletableFuture<Person> future = new CompletableFuture<>();
|
||||
future.complete(new Person("Joe"));
|
||||
|
|
|
|||
|
|
@ -0,0 +1,71 @@
|
|||
/*
|
||||
* Copyright 2002-2021 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.test.web.servlet.samples.client.standalone;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.test.StepVerifier;
|
||||
|
||||
import org.springframework.test.web.Person;
|
||||
import org.springframework.test.web.reactive.server.FluxExchangeResult;
|
||||
import org.springframework.test.web.reactive.server.WebTestClient;
|
||||
import org.springframework.test.web.servlet.client.MockMvcWebTestClient;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import static java.time.Duration.ofMillis;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* SSE controller tests with MockMvc and WebTestClient.
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
*/
|
||||
public class SseTests {
|
||||
|
||||
private final WebTestClient testClient =
|
||||
MockMvcWebTestClient.bindToController(new SseController()).build();
|
||||
|
||||
|
||||
@Test
|
||||
public void sse() {
|
||||
FluxExchangeResult<Person> exchangeResult = this.testClient.get()
|
||||
.uri("/persons")
|
||||
.exchange()
|
||||
.expectStatus().isOk()
|
||||
.expectHeader().contentType("text/event-stream")
|
||||
.returnResult(Person.class);
|
||||
|
||||
StepVerifier.create(exchangeResult.getResponseBody())
|
||||
.expectNext(new Person("N0"), new Person("N1"), new Person("N2"))
|
||||
.expectNextCount(4)
|
||||
.consumeNextWith(person -> assertThat(person.getName()).endsWith("7"))
|
||||
.thenCancel()
|
||||
.verify();
|
||||
}
|
||||
|
||||
|
||||
@RestController
|
||||
private static class SseController {
|
||||
|
||||
@GetMapping(path = "/persons", produces = "text/event-stream")
|
||||
public Flux<Person> getPersonStream() {
|
||||
return Flux.interval(ofMillis(100)).take(50).onBackpressureBuffer(50)
|
||||
.map(index -> new Person("N" + index));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -7471,12 +7471,35 @@ or reactive type such as Reactor `Mono`:
|
|||
[[spring-mvc-test-vs-streaming-response]]
|
||||
===== Streaming Responses
|
||||
|
||||
There are no options built into Spring MVC Test for container-less testing of streaming
|
||||
responses. However you can test streaming requests through the <<WebTestClient>>.
|
||||
This is also supported in Spring Boot where you can
|
||||
{doc-spring-boot}/html/spring-boot-features.html#boot-features-testing-spring-boot-applications-testing-with-running-server[test a running server]
|
||||
with `WebTestClient`. One extra advantage is the ability to use the `StepVerifier` from
|
||||
project Reactor that allows declaring expectations on a stream of data.
|
||||
The best way to test streaming responses such as Server-Sent Events is through the
|
||||
<<WebTestClient>> which can be used as a test client to connect to a `MockMvc` instance
|
||||
to perform tests on Spring MVC controllers without a running server. For example:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
WebTestClient client = MockMvcWebTestClient.bindToController(new SseController()).build();
|
||||
|
||||
FluxExchangeResult<Person> exchangeResult = client.get()
|
||||
.uri("/persons")
|
||||
.exchange()
|
||||
.expectStatus().isOk()
|
||||
.expectHeader().contentType("text/event-stream")
|
||||
.returnResult(Person.class);
|
||||
|
||||
// Use StepVerifier from Project Reactor to test the streaming response
|
||||
|
||||
StepVerifier.create(exchangeResult.getResponseBody())
|
||||
.expectNext(new Person("N0"), new Person("N1"), new Person("N2"))
|
||||
.expectNextCount(4)
|
||||
.consumeNextWith(person -> assertThat(person.getName()).endsWith("7"))
|
||||
.thenCancel()
|
||||
.verify();
|
||||
----
|
||||
|
||||
`WebTestClient` can also connect to a live server and perform full end-to-end integration
|
||||
tests. This is also supported in Spring Boot where you can
|
||||
{doc-spring-boot}/html/spring-boot-features.html#boot-features-testing-spring-boot-applications-testing-with-running-server[test a running server].
|
||||
|
||||
|
||||
[[spring-mvc-test-server-filters]]
|
||||
|
|
|
|||
Loading…
Reference in New Issue