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 | 68 +++++++++++++++++++++++++++------- 1 file changed, 55 insertions(+), 13 deletions(-) diff --git a/src/docs/asciidoc/testing.adoc b/src/docs/asciidoc/testing.adoc index be9e0f32d95..4dda101f04a 100644 --- a/src/docs/asciidoc/testing.adoc +++ b/src/docs/asciidoc/testing.adoc @@ -5115,22 +5115,46 @@ resulting links by using XPath expressions: ---- ==== -[[spring-mvc-test-server-filters]] -===== Filter Registrations -When setting up a `MockMvc` instance, you can register one or more Servlet `Filter` -instances, as the following example shows: +[[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"] ---- - mockMvc = standaloneSetup(new PersonController()).addFilters(new CharacterEncodingFilter()).build(); +@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 + ==== -Registered filters are invoked through the `MockFilterChain` from `spring-test`, and the -last filter delegates to the `DispatcherServlet`. + [[spring-mvc-test-vs-streaming-response]] @@ -5146,13 +5170,31 @@ with `WebTestClient`. One extra advantage is the ability to use the `StepVerifie project Reactor that allows declaring expectations on a stream of data. +[[spring-mvc-test-server-filters]] +===== Filter Registrations + +When setting up a `MockMvc` instance, you can register one or more Servlet `Filter` +instances, as the following example shows: + +==== +[source,java,indent=0] +[subs="verbatim,quotes"] +---- + mockMvc = standaloneSetup(new PersonController()).addFilters(new CharacterEncodingFilter()).build(); +---- +==== + +Registered filters are invoked through the `MockFilterChain` from `spring-test`, and the +last filter delegates to the `DispatcherServlet`. + + [[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 ebf2b041d57..629b16e600f 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 d750f93f162..b1a05f5c28b 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) {