diff --git a/spring-test/src/main/java/org/springframework/test/web/reactive/server/AbstractMockServerSpec.java b/spring-test/src/main/java/org/springframework/test/web/reactive/server/AbstractMockServerSpec.java index ffe95a5d33..8a1b5a0f89 100644 --- a/spring-test/src/main/java/org/springframework/test/web/reactive/server/AbstractMockServerSpec.java +++ b/spring-test/src/main/java/org/springframework/test/web/reactive/server/AbstractMockServerSpec.java @@ -35,6 +35,8 @@ abstract class AbstractMockServerSpec> private final List filters = new ArrayList<>(4); + private final List configurers = new ArrayList<>(4); + @Override public T webFilter(WebFilter... filter) { @@ -42,20 +44,23 @@ abstract class AbstractMockServerSpec> return self(); } + @Override + public T apply(MockServerConfigurer configurer) { + configurer.afterConfigureAdded(this); + this.configurers.add(configurer); + return self(); + } + @SuppressWarnings("unchecked") private T self() { return (T) this; } - @Override public WebTestClient.Builder configureClient() { WebHttpHandlerBuilder builder = initHttpHandlerBuilder(); - builder.filters(currentFilters -> { - List toPrepend = new ArrayList<>(this.filters); - Collections.reverse(toPrepend); - toPrepend.forEach(filter -> currentFilters.add(0, filter)); - }); + builder.filters(theFilters -> theFilters.addAll(0, this.filters)); + this.configurers.forEach(configurer -> configurer.beforeServerCreated(builder)); return new DefaultWebTestClientBuilder(builder.build()); } @@ -65,7 +70,6 @@ abstract class AbstractMockServerSpec> */ protected abstract WebHttpHandlerBuilder initHttpHandlerBuilder(); - @Override public WebTestClient build() { return configureClient().build(); diff --git a/spring-test/src/main/java/org/springframework/test/web/reactive/server/MockServerConfigurer.java b/spring-test/src/main/java/org/springframework/test/web/reactive/server/MockServerConfigurer.java new file mode 100644 index 0000000000..31994f8cb4 --- /dev/null +++ b/spring-test/src/main/java/org/springframework/test/web/reactive/server/MockServerConfigurer.java @@ -0,0 +1,60 @@ +/* + * Copyright 2002-2017 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 + * + * http://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.reactive.server; + +import org.springframework.web.server.adapter.WebHttpHandlerBuilder; + +/** + * Contract that frameworks or applications can use to pre-package a set of + * customizations to a {@link WebTestClient.MockServerSpec} and expose that + * as a shortcut. + * + *

An implementation of this interface can be plugged in via + * {@link WebTestClient.MockServerSpec#apply} where instances are likely obtained + * via static methods, e.g.: + * + *

+ * import static org.example.ExampleSetup.securitySetup;
+ *
+ * // ...
+ *
+ * WebTestClient.bindToController(new TestController())
+ *     .apply(securitySetup("foo","bar"))
+ *     .build();
+ * 
+ * + * @author Rossen Stoyanchev + * @since 5.0 + */ +public interface MockServerConfigurer { + + /** + * Invoked immediately, i.e. before this method returns. + * @param serverSpec the serverSpec to which the configurer is added + */ + default void afterConfigureAdded(WebTestClient.MockServerSpec serverSpec) { + } + + /** + * Invoked just before the mock server is built. Use this hook to inspect + * and/or modify application-declared filtes and exception handlers, + * @param builder the builder for the {@code HttpHandler} that will handle + * requests (i.e. the mock server) + */ + default void beforeServerCreated(WebHttpHandlerBuilder builder) { + } + +} diff --git a/spring-test/src/main/java/org/springframework/test/web/reactive/server/WebTestClient.java b/spring-test/src/main/java/org/springframework/test/web/reactive/server/WebTestClient.java index b72fa0dfc3..22f1d518c1 100644 --- a/spring-test/src/main/java/org/springframework/test/web/reactive/server/WebTestClient.java +++ b/spring-test/src/main/java/org/springframework/test/web/reactive/server/WebTestClient.java @@ -203,6 +203,12 @@ public interface WebTestClient { */ T webFilter(WebFilter... filter); + /** + * Shortcut for pre-packaged customizations to the mock server setup. + * @param configurer the configurer to apply + */ + T apply(MockServerConfigurer configurer); + /** * Proceed to configure and build the test client. */ diff --git a/spring-test/src/test/java/org/springframework/test/web/reactive/server/MockServerSpecTests.java b/spring-test/src/test/java/org/springframework/test/web/reactive/server/MockServerSpecTests.java new file mode 100644 index 0000000000..ebb2715741 --- /dev/null +++ b/spring-test/src/test/java/org/springframework/test/web/reactive/server/MockServerSpecTests.java @@ -0,0 +1,108 @@ +/* + * Copyright 2002-2017 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 + * + * http://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.reactive.server; + +import java.nio.charset.StandardCharsets; + +import org.junit.Test; +import reactor.core.publisher.Mono; + +import org.springframework.core.io.buffer.DataBuffer; +import org.springframework.core.io.buffer.DefaultDataBufferFactory; +import org.springframework.web.server.ServerWebExchange; +import org.springframework.web.server.WebFilter; +import org.springframework.web.server.WebFilterChain; +import org.springframework.web.server.adapter.WebHttpHandlerBuilder; + +/** + * Unit tests for {@link AbstractMockServerSpec}. + * @author Rossen Stoyanchev + */ +public class MockServerSpecTests { + + private final TestMockServerSpec serverSpec = new TestMockServerSpec(); + + + @Test + public void applyFiltersAfterConfigurerAdded() { + + this.serverSpec.webFilter(new TestWebFilter("A")); + + this.serverSpec.apply(new MockServerConfigurer() { + + @Override + public void afterConfigureAdded(WebTestClient.MockServerSpec spec) { + spec.webFilter(new TestWebFilter("B")); + } + }); + + this.serverSpec.build().get().uri("/").exchange().expectBody(String.class) + .isEqualTo("{test-attribute=:A:B}"); + } + + @Test + public void applyFiltersBeforeServerCreated() { + + this.serverSpec.webFilter(new TestWebFilter("App-A")); + this.serverSpec.webFilter(new TestWebFilter("App-B")); + + this.serverSpec.apply(new MockServerConfigurer() { + + @Override + public void beforeServerCreated(WebHttpHandlerBuilder builder) { + builder.filters(filters -> { + filters.add(0, new TestWebFilter("Fwk-A")); + filters.add(1, new TestWebFilter("Fwk-B")); + }); + } + }); + + this.serverSpec.build().get().uri("/").exchange().expectBody(String.class) + .isEqualTo("{test-attribute=:Fwk-A:Fwk-B:App-A:App-B}"); + } + + + private static class TestMockServerSpec extends AbstractMockServerSpec { + + @Override + protected WebHttpHandlerBuilder initHttpHandlerBuilder() { + return WebHttpHandlerBuilder.webHandler(exchange -> { + DefaultDataBufferFactory factory = new DefaultDataBufferFactory(); + String text = exchange.getAttributes().toString(); + DataBuffer buffer = factory.wrap(text.getBytes(StandardCharsets.UTF_8)); + return exchange.getResponse().writeWith(Mono.just(buffer)); + }); + } + } + + private static class TestWebFilter implements WebFilter { + + private final String name; + + TestWebFilter(String name) { + this.name = name; + } + + @Override + public Mono filter(ServerWebExchange exchange, WebFilterChain chain) { + String name = "test-attribute"; + String value = (String) exchange.getAttribute(name).orElse(""); + exchange.getAttributes().put(name, value + ":" + this.name); + return chain.filter(exchange); + } + } + +}