WebTestClient (server-less) setup accepts WebFilter's
Issue: SPR-15349
This commit is contained in:
		
							parent
							
								
									a99fe3eda4
								
							
						
					
					
						commit
						54192cf513
					
				| 
						 | 
					@ -16,10 +16,14 @@
 | 
				
			||||||
 | 
					
 | 
				
			||||||
package org.springframework.test.web.reactive.server;
 | 
					package org.springframework.test.web.reactive.server;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import java.util.ArrayList;
 | 
				
			||||||
 | 
					import java.util.Arrays;
 | 
				
			||||||
 | 
					import java.util.Collections;
 | 
				
			||||||
 | 
					import java.util.List;
 | 
				
			||||||
import java.util.function.UnaryOperator;
 | 
					import java.util.function.UnaryOperator;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import org.springframework.http.server.reactive.HttpHandler;
 | 
					 | 
				
			||||||
import org.springframework.web.server.ServerWebExchange;
 | 
					import org.springframework.web.server.ServerWebExchange;
 | 
				
			||||||
 | 
					import org.springframework.web.server.WebFilter;
 | 
				
			||||||
import org.springframework.web.server.adapter.WebHttpHandlerBuilder;
 | 
					import org.springframework.web.server.adapter.WebHttpHandlerBuilder;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
| 
						 | 
					@ -33,6 +37,13 @@ abstract class AbstractMockServerSpec<B extends WebTestClient.MockServerSpec<B>>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	private final ExchangeMutatorWebFilter exchangeMutatorFilter = new ExchangeMutatorWebFilter();
 | 
						private final ExchangeMutatorWebFilter exchangeMutatorFilter = new ExchangeMutatorWebFilter();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						private final List<WebFilter> filters = new ArrayList<>(4);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						AbstractMockServerSpec() {
 | 
				
			||||||
 | 
							this.filters.add(this.exchangeMutatorFilter);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	@Override
 | 
						@Override
 | 
				
			||||||
	public <T extends B> T exchangeMutator(UnaryOperator<ServerWebExchange> mutator) {
 | 
						public <T extends B> T exchangeMutator(UnaryOperator<ServerWebExchange> mutator) {
 | 
				
			||||||
| 
						 | 
					@ -40,6 +51,12 @@ abstract class AbstractMockServerSpec<B extends WebTestClient.MockServerSpec<B>>
 | 
				
			||||||
		return self();
 | 
							return self();
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						@Override
 | 
				
			||||||
 | 
						public <T extends B> T webFilter(WebFilter... filter) {
 | 
				
			||||||
 | 
							this.filters.addAll(Arrays.asList(filter));
 | 
				
			||||||
 | 
							return self();
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	@SuppressWarnings("unchecked")
 | 
						@SuppressWarnings("unchecked")
 | 
				
			||||||
	private <T extends B> T self() {
 | 
						private <T extends B> T self() {
 | 
				
			||||||
		return (T) this;
 | 
							return (T) this;
 | 
				
			||||||
| 
						 | 
					@ -48,12 +65,25 @@ abstract class AbstractMockServerSpec<B extends WebTestClient.MockServerSpec<B>>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	@Override
 | 
						@Override
 | 
				
			||||||
	public WebTestClient.Builder configureClient() {
 | 
						public WebTestClient.Builder configureClient() {
 | 
				
			||||||
		HttpHandler handler = initHttpHandlerBuilder().prependFilter(this.exchangeMutatorFilter).build();
 | 
							WebHttpHandlerBuilder builder = initHttpHandlerBuilder();
 | 
				
			||||||
		return new DefaultWebTestClientBuilder(handler, this.exchangeMutatorFilter);
 | 
							filtersInReverse().forEach(builder::prependFilter);
 | 
				
			||||||
 | 
							return new DefaultWebTestClientBuilder(builder.build(), this.exchangeMutatorFilter);
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/**
 | 
				
			||||||
 | 
						 * Sub-classes to create the {@code WebHttpHandlerBuilder} to use.
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
	protected abstract WebHttpHandlerBuilder initHttpHandlerBuilder();
 | 
						protected abstract WebHttpHandlerBuilder initHttpHandlerBuilder();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/**
 | 
				
			||||||
 | 
						 * Return the filters in reverse order for pre-pending.
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
						private List<WebFilter> filtersInReverse() {
 | 
				
			||||||
 | 
							List<WebFilter> result = new ArrayList<>(this.filters);
 | 
				
			||||||
 | 
							Collections.reverse(result);
 | 
				
			||||||
 | 
							return result;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	@Override
 | 
						@Override
 | 
				
			||||||
	public WebTestClient build() {
 | 
						public WebTestClient build() {
 | 
				
			||||||
		return configureClient().build();
 | 
							return configureClient().build();
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -50,6 +50,7 @@ import org.springframework.web.reactive.function.client.ExchangeStrategies;
 | 
				
			||||||
import org.springframework.web.reactive.function.client.WebClient;
 | 
					import org.springframework.web.reactive.function.client.WebClient;
 | 
				
			||||||
import org.springframework.web.reactive.function.server.RouterFunction;
 | 
					import org.springframework.web.reactive.function.server.RouterFunction;
 | 
				
			||||||
import org.springframework.web.server.ServerWebExchange;
 | 
					import org.springframework.web.server.ServerWebExchange;
 | 
				
			||||||
 | 
					import org.springframework.web.server.WebFilter;
 | 
				
			||||||
import org.springframework.web.util.UriBuilder;
 | 
					import org.springframework.web.util.UriBuilder;
 | 
				
			||||||
import org.springframework.web.util.UriBuilderFactory;
 | 
					import org.springframework.web.util.UriBuilderFactory;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -193,6 +194,12 @@ public interface WebTestClient {
 | 
				
			||||||
		 */
 | 
							 */
 | 
				
			||||||
		<T extends B> T exchangeMutator(UnaryOperator<ServerWebExchange> mutator);
 | 
							<T extends B> T exchangeMutator(UnaryOperator<ServerWebExchange> mutator);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							/**
 | 
				
			||||||
 | 
							 * Configure {@link WebFilter}'s for server request processing.
 | 
				
			||||||
 | 
							 * @param filter one or more filters
 | 
				
			||||||
 | 
							 */
 | 
				
			||||||
 | 
							<T extends B> T webFilter(WebFilter... filter);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		/**
 | 
							/**
 | 
				
			||||||
		 * Proceed to configure and build the test client.
 | 
							 * Proceed to configure and build the test client.
 | 
				
			||||||
		 */
 | 
							 */
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -32,9 +32,7 @@ import org.springframework.web.bind.annotation.RequestAttribute;
 | 
				
			||||||
import org.springframework.web.bind.annotation.RestController;
 | 
					import org.springframework.web.bind.annotation.RestController;
 | 
				
			||||||
import org.springframework.web.reactive.config.EnableWebFlux;
 | 
					import org.springframework.web.reactive.config.EnableWebFlux;
 | 
				
			||||||
import org.springframework.web.server.ServerWebExchange;
 | 
					import org.springframework.web.server.ServerWebExchange;
 | 
				
			||||||
 | 
					import org.springframework.web.server.WebFilter;
 | 
				
			||||||
import static org.mockito.Mockito.mock;
 | 
					 | 
				
			||||||
import static org.mockito.Mockito.when;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * Binding to server infrastructure declared in a Spring ApplicationContext.
 | 
					 * Binding to server infrastructure declared in a Spring ApplicationContext.
 | 
				
			||||||
| 
						 | 
					@ -56,24 +54,16 @@ public class ApplicationContextTests {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		this.client = WebTestClient.bindToApplicationContext(context)
 | 
							this.client = WebTestClient.bindToApplicationContext(context)
 | 
				
			||||||
				.exchangeMutator(principal("Pablo"))
 | 
									.exchangeMutator(principal("Pablo"))
 | 
				
			||||||
 | 
									.webFilter(prefixFilter("Mr."))
 | 
				
			||||||
				.build();
 | 
									.build();
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	private UnaryOperator<ServerWebExchange> principal(String userName) {
 | 
					 | 
				
			||||||
		return exchange -> {
 | 
					 | 
				
			||||||
			Principal user = mock(Principal.class);
 | 
					 | 
				
			||||||
			when(user.getName()).thenReturn(userName);
 | 
					 | 
				
			||||||
			return exchange.mutate().principal(Mono.just(user)).build();
 | 
					 | 
				
			||||||
		};
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	@Test
 | 
						@Test
 | 
				
			||||||
	public void basic() throws Exception {
 | 
						public void basic() throws Exception {
 | 
				
			||||||
		this.client.get().uri("/principal")
 | 
							this.client.get().uri("/principal")
 | 
				
			||||||
				.exchange()
 | 
									.exchange()
 | 
				
			||||||
				.expectStatus().isOk()
 | 
									.expectStatus().isOk()
 | 
				
			||||||
				.expectBody(String.class).value().isEqualTo("Hello Pablo!");
 | 
									.expectBody(String.class).value().isEqualTo("Hello Mr. Pablo!");
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	@Test
 | 
						@Test
 | 
				
			||||||
| 
						 | 
					@ -82,7 +72,7 @@ public class ApplicationContextTests {
 | 
				
			||||||
				.get().uri("/principal")
 | 
									.get().uri("/principal")
 | 
				
			||||||
				.exchange()
 | 
									.exchange()
 | 
				
			||||||
				.expectStatus().isOk()
 | 
									.expectStatus().isOk()
 | 
				
			||||||
				.expectBody(String.class).value().isEqualTo("Hello Giovanni!");
 | 
									.expectBody(String.class).value().isEqualTo("Hello Mr. Giovanni!");
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	@Test
 | 
						@Test
 | 
				
			||||||
| 
						 | 
					@ -96,6 +86,18 @@ public class ApplicationContextTests {
 | 
				
			||||||
				.expectBody(String.class).value().isEqualTo("foo+bar");
 | 
									.expectBody(String.class).value().isEqualTo("foo+bar");
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						private UnaryOperator<ServerWebExchange> principal(String userName) {
 | 
				
			||||||
 | 
							return exchange -> exchange.mutate().principal(Mono.just(new TestUser(userName))).build();
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						private WebFilter prefixFilter(String prefix) {
 | 
				
			||||||
 | 
							return (exchange, chain) -> {
 | 
				
			||||||
 | 
								Mono<Principal> user = exchange.getPrincipal().map(p -> new TestUser(prefix + " " + p.getName()));
 | 
				
			||||||
 | 
								return chain.filter(exchange.mutate().principal(user).build());
 | 
				
			||||||
 | 
							};
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	private UnaryOperator<ServerWebExchange> attribute(String attrName, String attrValue) {
 | 
						private UnaryOperator<ServerWebExchange> attribute(String attrName, String attrValue) {
 | 
				
			||||||
		return exchange -> {
 | 
							return exchange -> {
 | 
				
			||||||
			exchange.getAttributes().put(attrName, attrValue);
 | 
								exchange.getAttributes().put(attrName, attrValue);
 | 
				
			||||||
| 
						 | 
					@ -129,4 +131,18 @@ public class ApplicationContextTests {
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						private static class TestUser implements Principal {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							private final String name;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							TestUser(String name) {
 | 
				
			||||||
 | 
								this.name = name;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							@Override
 | 
				
			||||||
 | 
							public String getName() {
 | 
				
			||||||
 | 
								return this.name;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -27,9 +27,7 @@ import org.springframework.web.bind.annotation.GetMapping;
 | 
				
			||||||
import org.springframework.web.bind.annotation.RequestAttribute;
 | 
					import org.springframework.web.bind.annotation.RequestAttribute;
 | 
				
			||||||
import org.springframework.web.bind.annotation.RestController;
 | 
					import org.springframework.web.bind.annotation.RestController;
 | 
				
			||||||
import org.springframework.web.server.ServerWebExchange;
 | 
					import org.springframework.web.server.ServerWebExchange;
 | 
				
			||||||
 | 
					import org.springframework.web.server.WebFilter;
 | 
				
			||||||
import static org.mockito.Mockito.mock;
 | 
					 | 
				
			||||||
import static org.mockito.Mockito.when;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * Bind to annotated controllers.
 | 
					 * Bind to annotated controllers.
 | 
				
			||||||
| 
						 | 
					@ -39,27 +37,18 @@ import static org.mockito.Mockito.when;
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
public class ControllerTests {
 | 
					public class ControllerTests {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	private final WebTestClient client = WebTestClient
 | 
						private final WebTestClient client = WebTestClient.bindToController(new TestController())
 | 
				
			||||||
			.bindToController(new TestController())
 | 
					 | 
				
			||||||
			.exchangeMutator(principal("Pablo"))
 | 
								.exchangeMutator(principal("Pablo"))
 | 
				
			||||||
 | 
								.webFilter(prefixFilter("Mr."))
 | 
				
			||||||
			.build();
 | 
								.build();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	private UnaryOperator<ServerWebExchange> principal(String userName) {
 | 
					 | 
				
			||||||
		return exchange -> {
 | 
					 | 
				
			||||||
			Principal user = mock(Principal.class);
 | 
					 | 
				
			||||||
			when(user.getName()).thenReturn(userName);
 | 
					 | 
				
			||||||
			return exchange.mutate().principal(Mono.just(user)).build();
 | 
					 | 
				
			||||||
		};
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	@Test
 | 
						@Test
 | 
				
			||||||
	public void basic() throws Exception {
 | 
						public void basic() throws Exception {
 | 
				
			||||||
		this.client.get().uri("/principal")
 | 
							this.client.get().uri("/principal")
 | 
				
			||||||
				.exchange()
 | 
									.exchange()
 | 
				
			||||||
				.expectStatus().isOk()
 | 
									.expectStatus().isOk()
 | 
				
			||||||
				.expectBody(String.class).value().isEqualTo("Hello Pablo!");
 | 
									.expectBody(String.class).value().isEqualTo("Hello Mr. Pablo!");
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	@Test
 | 
						@Test
 | 
				
			||||||
| 
						 | 
					@ -68,7 +57,7 @@ public class ControllerTests {
 | 
				
			||||||
				.get().uri("/principal")
 | 
									.get().uri("/principal")
 | 
				
			||||||
				.exchange()
 | 
									.exchange()
 | 
				
			||||||
				.expectStatus().isOk()
 | 
									.expectStatus().isOk()
 | 
				
			||||||
				.expectBody(String.class).value().isEqualTo("Hello Giovanni!");
 | 
									.expectBody(String.class).value().isEqualTo("Hello Mr. Giovanni!");
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	@Test
 | 
						@Test
 | 
				
			||||||
| 
						 | 
					@ -82,6 +71,18 @@ public class ControllerTests {
 | 
				
			||||||
				.expectBody(String.class).value().isEqualTo("foo+bar");
 | 
									.expectBody(String.class).value().isEqualTo("foo+bar");
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						private UnaryOperator<ServerWebExchange> principal(String userName) {
 | 
				
			||||||
 | 
							return exchange -> exchange.mutate().principal(Mono.just(new TestUser(userName))).build();
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						private WebFilter prefixFilter(String prefix) {
 | 
				
			||||||
 | 
							return (exchange, chain) -> {
 | 
				
			||||||
 | 
								Mono<Principal> user = exchange.getPrincipal().map(p -> new TestUser(prefix + " " + p.getName()));
 | 
				
			||||||
 | 
								return chain.filter(exchange.mutate().principal(user).build());
 | 
				
			||||||
 | 
							};
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	private UnaryOperator<ServerWebExchange> attribute(String attrName, String attrValue) {
 | 
						private UnaryOperator<ServerWebExchange> attribute(String attrName, String attrValue) {
 | 
				
			||||||
		return exchange -> {
 | 
							return exchange -> {
 | 
				
			||||||
			exchange.getAttributes().put(attrName, attrValue);
 | 
								exchange.getAttributes().put(attrName, attrValue);
 | 
				
			||||||
| 
						 | 
					@ -104,4 +105,18 @@ public class ControllerTests {
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						private static class TestUser implements Principal {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							private final String name;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							TestUser(String name) {
 | 
				
			||||||
 | 
								this.name = name;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							@Override
 | 
				
			||||||
 | 
							public String getName() {
 | 
				
			||||||
 | 
								return this.name;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
		Reference in New Issue