Skip to content

Commit

Permalink
Add testing support for WebMvc.fn
Browse files Browse the repository at this point in the history
This commit introduces testing support for WebMvc.fn in the form of a
RouterFunctionMockMvcBuilder and RouterFunctionMockMvcSpec.

Closes gh-30477
  • Loading branch information
poutsma committed May 2, 2024
1 parent d708343 commit 8f3b748
Show file tree
Hide file tree
Showing 9 changed files with 875 additions and 24 deletions.
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* Copyright 2002-2024 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.
Expand Down Expand Up @@ -35,6 +35,7 @@
import org.springframework.test.web.servlet.ResultMatcher;
import org.springframework.test.web.servlet.setup.ConfigurableMockMvcBuilder;
import org.springframework.test.web.servlet.setup.MockMvcConfigurer;
import org.springframework.test.web.servlet.setup.RouterFunctionMockMvcBuilder;
import org.springframework.test.web.servlet.setup.StandaloneMockMvcBuilder;
import org.springframework.validation.Validator;
import org.springframework.web.accept.ContentNegotiationManager;
Expand All @@ -47,6 +48,7 @@
import org.springframework.web.servlet.LocaleResolver;
import org.springframework.web.servlet.View;
import org.springframework.web.servlet.ViewResolver;
import org.springframework.web.servlet.function.RouterFunction;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
import org.springframework.web.util.pattern.PathPatternParser;

Expand Down Expand Up @@ -81,13 +83,25 @@ public interface MockMvcWebTestClient {
* Begin creating a {@link WebTestClient} by providing the {@code @Controller}
* instance(s) to handle requests with.
* <p>Internally this is delegated to and equivalent to using
* {@link org.springframework.test.web.servlet.setup.MockMvcBuilders#standaloneSetup(Object...)}.
* {@link org.springframework.test.web.servlet.setup.MockMvcBuilders#standaloneSetup(Object...)}
* to initialize {@link MockMvc}.
*/
static ControllerSpec bindToController(Object... controllers) {
return new StandaloneMockMvcSpec(controllers);
}

/**
* Begin creating a {@link WebTestClient} by providing the {@link RouterFunction}
* instance(s) to handle requests with.
* <p>Internally this is delegated to and equivalent to using
* {@link org.springframework.test.web.servlet.setup.MockMvcBuilders#routerFunctions(RouterFunction[])}
* to initialize {@link MockMvc}.
* @since 6.2
*/
static RouterFunctionSpec bindToRouterFunction(RouterFunction<?>... routerFunctions) {
return new RouterFunctionMockMvcSpec(routerFunctions);
}

/**
* Begin creating a {@link WebTestClient} by providing a
* {@link WebApplicationContext} with Spring MVC infrastructure and
Expand Down Expand Up @@ -381,4 +395,72 @@ ControllerSpec mappedInterceptors(
ControllerSpec customHandlerMapping(Supplier<RequestMappingHandlerMapping> factory);
}


/**
* Specification for configuring {@link MockMvc} to test one or more
* {@linkplain RouterFunction router functions}
* directly, and a simple facade around {@link RouterFunctionMockMvcBuilder}.
* @since 6.2
*/
interface RouterFunctionSpec extends MockMvcServerSpec<RouterFunctionSpec> {

/**
* Set the message converters to use.
* <p>This is delegated to
* {@link RouterFunctionMockMvcBuilder#setMessageConverters(HttpMessageConverter[])}.
*/
RouterFunctionSpec messageConverters(HttpMessageConverter<?>... messageConverters);

/**
* Add global interceptors.
* <p>This is delegated to
* {@link RouterFunctionMockMvcBuilder#addInterceptors(HandlerInterceptor...)}.
*/
RouterFunctionSpec interceptors(HandlerInterceptor... interceptors);

/**
* Add interceptors for specific patterns.
* <p>This is delegated to
* {@link RouterFunctionMockMvcBuilder#addMappedInterceptors(String[], HandlerInterceptor...)}.
*/
RouterFunctionSpec mappedInterceptors(
@Nullable String[] pathPatterns, HandlerInterceptor... interceptors);

/**
* Specify the timeout value for async execution.
* <p>This is delegated to
* {@link RouterFunctionMockMvcBuilder#setAsyncRequestTimeout(long)}.
*/
RouterFunctionSpec asyncRequestTimeout(long timeout);

/**
* Set the HandlerExceptionResolver types to use.
* <p>This is delegated to
* {@link RouterFunctionMockMvcBuilder#setHandlerExceptionResolvers(HandlerExceptionResolver...)}.
*/
RouterFunctionSpec handlerExceptionResolvers(HandlerExceptionResolver... exceptionResolvers);

/**
* Set up view resolution.
* <p>This is delegated to
* {@link RouterFunctionMockMvcBuilder#setViewResolvers(ViewResolver...)}.
*/
RouterFunctionSpec viewResolvers(ViewResolver... resolvers);

/**
* Set up a single {@link ViewResolver} with a fixed view.
* <p>This is delegated to
* {@link RouterFunctionMockMvcBuilder#setSingleView(View)}.
*/
RouterFunctionSpec singleView(View view);

/**
* Enable URL path matching with parsed
* {@link org.springframework.web.util.pattern.PathPattern PathPatterns}.
* <p>This is delegated to
* {@link RouterFunctionMockMvcBuilder#setPatternParser(PathPatternParser)}.
*/
RouterFunctionSpec patternParser(PathPatternParser parser);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
/*
* Copyright 2002-2024 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.client;

import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.lang.Nullable;
import org.springframework.test.web.servlet.setup.ConfigurableMockMvcBuilder;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.test.web.servlet.setup.RouterFunctionMockMvcBuilder;
import org.springframework.web.servlet.HandlerExceptionResolver;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.View;
import org.springframework.web.servlet.ViewResolver;
import org.springframework.web.servlet.function.RouterFunction;
import org.springframework.web.util.pattern.PathPatternParser;

/**
* Simple wrapper around a {@link RouterFunctionMockMvcBuilder} that implements
* {@link MockMvcWebTestClient.RouterFunctionSpec}.
*
* @author Arjen Poutsma
* @since 6.2
*/
class RouterFunctionMockMvcSpec extends AbstractMockMvcServerSpec<MockMvcWebTestClient.RouterFunctionSpec>
implements MockMvcWebTestClient.RouterFunctionSpec {

private final RouterFunctionMockMvcBuilder mockMvcBuilder;


RouterFunctionMockMvcSpec(RouterFunction<?>... routerFunctions) {
this.mockMvcBuilder = MockMvcBuilders.routerFunctions(routerFunctions);
}

@Override
public MockMvcWebTestClient.RouterFunctionSpec messageConverters(HttpMessageConverter<?>... messageConverters) {
this.mockMvcBuilder.setMessageConverters(messageConverters);
return this;
}

@Override
public MockMvcWebTestClient.RouterFunctionSpec interceptors(HandlerInterceptor... interceptors) {
mappedInterceptors(null, interceptors);
return this;
}

@Override
public MockMvcWebTestClient.RouterFunctionSpec mappedInterceptors(@Nullable String[] pathPatterns, HandlerInterceptor... interceptors) {
this.mockMvcBuilder.addMappedInterceptors(pathPatterns, interceptors);
return this;
}

@Override
public MockMvcWebTestClient.RouterFunctionSpec asyncRequestTimeout(long timeout) {
this.mockMvcBuilder.setAsyncRequestTimeout(timeout);
return this;
}

@Override
public MockMvcWebTestClient.RouterFunctionSpec handlerExceptionResolvers(HandlerExceptionResolver... exceptionResolvers) {
this.mockMvcBuilder.setHandlerExceptionResolvers(exceptionResolvers);
return this;
}

@Override
public MockMvcWebTestClient.RouterFunctionSpec viewResolvers(ViewResolver... resolvers) {
this.mockMvcBuilder.setViewResolvers(resolvers);
return this;
}

@Override
public MockMvcWebTestClient.RouterFunctionSpec singleView(View view) {
this.mockMvcBuilder.setSingleView(view);
return this;
}

@Override
public MockMvcWebTestClient.RouterFunctionSpec patternParser(PathPatternParser parser) {
this.mockMvcBuilder.setPatternParser(parser);
return this;
}

@Override
protected ConfigurableMockMvcBuilder<?> getMockMvcBuilder() {
return this.mockMvcBuilder;
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2018 the original author or authors.
* Copyright 2002-2024 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.
Expand All @@ -19,6 +19,7 @@
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.MockMvcBuilder;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.servlet.function.RouterFunction;

/**
* The main class to import in order to access all available {@link MockMvcBuilder MockMvcBuilders}.
Expand Down Expand Up @@ -76,4 +77,23 @@ public static StandaloneMockMvcBuilder standaloneSetup(Object... controllers) {
return new StandaloneMockMvcBuilder(controllers);
}

/**
* Build a {@link MockMvc} instance by registering one or more
* {@link RouterFunction RouterFunction} instances and configuring Spring
* MVC infrastructure programmatically.
* <p>This allows full control over the instantiation and initialization of
* router functions and their dependencies, similar to plain unit tests while
* also making it possible to test one router function at a time.
* <p>When this builder is used, the minimum infrastructure required by the
* {@link org.springframework.web.servlet.DispatcherServlet DispatcherServlet}
* to serve requests with router functions is created automatically
* and can be customized, resulting in configuration that is equivalent to
* what MVC Java configuration provides except using builder-style methods.
* @param routerFunctions one or more {@code RouterFunction} instances to test
* @since 6.2
*/
public static RouterFunctionMockMvcBuilder routerFunctions(RouterFunction<?>... routerFunctions) {
return new RouterFunctionMockMvcBuilder(routerFunctions);
}

}
Loading

0 comments on commit 8f3b748

Please sign in to comment.