From 2088a3d57b83c577b2322c98d9f0c6d20c857f23 Mon Sep 17 00:00:00 2001 From: Rossen Stoyanchev Date: Fri, 5 Jul 2019 10:30:02 +0100 Subject: [PATCH 1/2] Document async requests with Spring MVC Test Closes gh-19666 --- src/docs/asciidoc/testing.adoc | 80 ++++++++++++++++++++++++++-------- 1 file changed, 61 insertions(+), 19 deletions(-) diff --git a/src/docs/asciidoc/testing.adoc b/src/docs/asciidoc/testing.adoc index be9e0f32d9..4dda101f04 100644 --- a/src/docs/asciidoc/testing.adoc +++ b/src/docs/asciidoc/testing.adoc @@ -5115,6 +5115,61 @@ resulting links by using XPath expressions: ---- ==== + +[[spring-mvc-test-async-requests]] +===== Async Requests + +Servlet 3.0 asynchronous requests, +<>, work by exiting the Servlet container +thread and allowing the application to compute the response asynchronously, after which +an async dispatch is made to complete processing on a Servlet container thread. + +In Spring MVC Test, async requests can be tested by asserting the produced async value +first, then manually performing the async dispatch, and finally verifying the response. +Below is an example test for controller methods that return `DeferredResult`, `Callable`, +or reactive type such as Reactor `Mono`: + +==== +[source,java,indent=0] +[subs="verbatim,quotes"] +---- +@Test +public void test() throws Exception { + MvcResult mvcResult = this.mockMvc.perform(get("/path")) + .andExpect(status().isOk()) <1> + .andExpect(request().asyncStarted()) <2> + .andExpect(request().asyncResult("body")) <3> + .andReturn(); + + this.mockMvc.perform(asyncDispatch(mvcResult)) <4> + .andExpect(status().isOk()) <5> + .andExpect(content().string("body")); +} +---- +<1> Check response status is still unchanged +<2> Async processing must have started +<3> Wait and assert the async result +<4> Manually perform an ASYNC dispatch (as there is no running container) +<5> Verify the final response + +==== + + + + +[[spring-mvc-test-vs-streaming-response]] +===== Streaming Responses + +There are no options built into Spring MVC Test for container-less testing of streaming +responses. Applications that make use of +<> options can use the +<> to perform end-to-end, integration +tests against a running server. This is also supported in Spring Boot where you can +https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#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. + + [[spring-mvc-test-server-filters]] ===== Filter Registrations @@ -5133,26 +5188,13 @@ Registered filters are invoked through the `MockFilterChain` from `spring-test`, last filter delegates to the `DispatcherServlet`. -[[spring-mvc-test-vs-streaming-response]] -===== Streaming Responses - -There are no options built into Spring MVC Test for container-less testing of streaming -responses. Applications that make use of -<> options can use the -<> to perform end-to-end, integration -tests against a running server. This is also supported in Spring Boot where you can -https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#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. - - [[spring-mvc-test-vs-end-to-end-integration-tests]] -===== Differences Between Out-of-Container and End-to-End Integration Tests +===== Spring MVC Test vs End-to-End Tests -As mentioned earlier Spring MVC Test is built on the Servlet API mock objects from the -`spring-test` module and does not use a running Servlet container. Therefore, there are -some important differences compared to full end-to-end integration tests with an actual -client and server running. +Spring MVC Test is built on Servlet API mock implementations from the +`spring-test` module and does not rely on a running container. Therefore, there are +some differences when compared to full end-to-end integration tests with an actual +client and a live server running. The easiest way to think about this is by starting with a blank `MockHttpServletRequest`. Whatever you add to it is what the request becomes. Things that may catch you by surprise @@ -5193,7 +5235,7 @@ important thing to check. In short, there is room here for multiple styles and s of testing even within the same project. [[spring-mvc-test-server-resources]] -===== Further Server-Side Test Examples +===== Further Examples The framework's own tests include https://github.com/spring-projects/spring-framework/tree/master/spring-test/src/test/java/org/springframework/test/web/servlet/samples[many From 2aec175ccc5a5c3cbcd152697956ee5da90e5214 Mon Sep 17 00:00:00 2001 From: Rossen Stoyanchev Date: Fri, 5 Jul 2019 10:48:49 +0100 Subject: [PATCH 2/2] Drain body after exception from applying onStatus Closes gh-23230 --- .../reactive/function/client/DefaultWebClient.java | 14 ++++++++++---- .../client/WebClientDataBufferAllocatingTests.java | 11 ++++++++++- 2 files changed, 20 insertions(+), 5 deletions(-) diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/DefaultWebClient.java b/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/DefaultWebClient.java index ebf2b041d5..629b16e600 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/DefaultWebClient.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/DefaultWebClient.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2018 the original author or authors. + * Copyright 2002-2019 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. @@ -455,9 +455,15 @@ class DefaultWebClient implements WebClient { for (StatusHandler handler : this.statusHandlers) { if (handler.test(response.statusCode())) { HttpRequest request = this.requestSupplier.get(); - Mono exMono = handler.apply(response, request); - exMono = exMono.flatMap(ex -> drainBody(response, ex)); - exMono = exMono.onErrorResume(ex -> drainBody(response, ex)); + Mono exMono; + try { + exMono = handler.apply(response, request); + exMono = exMono.flatMap(ex -> drainBody(response, ex)); + exMono = exMono.onErrorResume(ex -> drainBody(response, ex)); + } + catch (Throwable ex2) { + exMono = drainBody(response, ex2); + } return errorFunction.apply(exMono); } } diff --git a/spring-webflux/src/test/java/org/springframework/web/reactive/function/client/WebClientDataBufferAllocatingTests.java b/spring-webflux/src/test/java/org/springframework/web/reactive/function/client/WebClientDataBufferAllocatingTests.java index d750f93f16..b1a05f5c28 100644 --- a/spring-webflux/src/test/java/org/springframework/web/reactive/function/client/WebClientDataBufferAllocatingTests.java +++ b/spring-webflux/src/test/java/org/springframework/web/reactive/function/client/WebClientDataBufferAllocatingTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2018 the original author or authors. + * Copyright 2002-2019 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. @@ -147,6 +147,15 @@ public class WebClientDataBufferAllocatingTests extends AbstractDataBufferAlloca testOnStatus(ex, response -> response.bodyToMono(Void.class).then(Mono.error(ex))); } + @Test // gh-23230 + public void onStatusWithImmediateErrorAndBodyNotConsumed() { + RuntimeException ex = new RuntimeException("response error"); + testOnStatus(ex, response -> { + throw ex; + }); + } + + private void testOnStatus(Throwable expected, Function> exceptionFunction) {