/* * Copyright 2012-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.boot.autoconfigure.web.servlet; import java.lang.reflect.Field; import java.util.ArrayList; import java.util.Arrays; import java.util.Date; import java.util.LinkedHashMap; import java.util.List; import java.util.Locale; import java.util.Map; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.validation.ValidatorFactory; import org.assertj.core.api.Condition; import org.joda.time.DateTime; import org.junit.After; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; import org.springframework.beans.DirectFieldAccessor; import org.springframework.beans.factory.NoSuchBeanDefinitionException; import org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration; import org.springframework.boot.autoconfigure.http.HttpMessageConverters; import org.springframework.boot.autoconfigure.http.HttpMessageConvertersAutoConfiguration; import org.springframework.boot.autoconfigure.validation.ValidationAutoConfiguration; import org.springframework.boot.autoconfigure.validation.ValidatorAdapter; import org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration.WebMvcAutoConfigurationAdapter; import org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration.WelcomePageHandlerMapping; import org.springframework.boot.test.util.EnvironmentTestUtils; import org.springframework.boot.web.server.WebServerFactoryCustomizerBeanPostProcessor; import org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext; import org.springframework.boot.web.servlet.filter.OrderedHttpPutFormContentFilter; import org.springframework.boot.web.servlet.server.MockServletWebServerFactory; import org.springframework.boot.web.servlet.server.ServletWebServerFactory; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; import org.springframework.core.io.ClassPathResource; import org.springframework.core.io.Resource; import org.springframework.format.support.FormattingConversionService; import org.springframework.http.HttpHeaders; import org.springframework.http.MediaType; import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.test.util.ReflectionTestUtils; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.setup.MockMvcBuilders; import org.springframework.util.ObjectUtils; import org.springframework.util.ReflectionUtils; import org.springframework.util.StringUtils; import org.springframework.validation.Validator; import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean; import org.springframework.web.accept.ContentNegotiationManager; import org.springframework.web.bind.support.ConfigurableWebBindingInitializer; import org.springframework.web.filter.HttpPutFormContentFilter; import org.springframework.web.servlet.HandlerAdapter; import org.springframework.web.servlet.HandlerExceptionResolver; import org.springframework.web.servlet.HandlerMapping; import org.springframework.web.servlet.LocaleResolver; import org.springframework.web.servlet.View; import org.springframework.web.servlet.ViewResolver; import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; import org.springframework.web.servlet.handler.HandlerExceptionResolverComposite; import org.springframework.web.servlet.handler.SimpleUrlHandlerMapping; import org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver; import org.springframework.web.servlet.i18n.FixedLocaleResolver; import org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter; import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter; import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping; import org.springframework.web.servlet.resource.AppCacheManifestTransformer; import org.springframework.web.servlet.resource.CachingResourceResolver; import org.springframework.web.servlet.resource.CachingResourceTransformer; import org.springframework.web.servlet.resource.ContentVersionStrategy; import org.springframework.web.servlet.resource.CssLinkResourceTransformer; import org.springframework.web.servlet.resource.FixedVersionStrategy; import org.springframework.web.servlet.resource.GzipResourceResolver; import org.springframework.web.servlet.resource.PathResourceResolver; import org.springframework.web.servlet.resource.ResourceHttpRequestHandler; import org.springframework.web.servlet.resource.ResourceResolver; import org.springframework.web.servlet.resource.ResourceTransformer; import org.springframework.web.servlet.resource.VersionResourceResolver; import org.springframework.web.servlet.view.AbstractView; import org.springframework.web.servlet.view.ContentNegotiatingViewResolver; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.mock; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.forwardedUrl; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; /** * Tests for {@link WebMvcAutoConfiguration}. * * @author Phillip Webb * @author Dave Syer * @author Andy Wilkinson * @author Stephane Nicoll * @author Brian Clozel * @author EddĂș MelĂ©ndez */ public class WebMvcAutoConfigurationTests { private static final MockServletWebServerFactory webServerFactory = new MockServletWebServerFactory(); @Rule public ExpectedException thrown = ExpectedException.none(); private AnnotationConfigServletWebServerApplicationContext context; @After public void close() { if (this.context != null) { this.context.close(); } } @Test public void handlerAdaptersCreated() throws Exception { load(); assertThat(this.context.getBeanNamesForType(HandlerAdapter.class).length) .isEqualTo(3); assertThat(this.context.getBean(RequestMappingHandlerAdapter.class) .getMessageConverters()).isNotEmpty() .isEqualTo(this.context.getBean(HttpMessageConverters.class) .getConverters()); } @Test public void handlerMappingsCreated() throws Exception { load(); assertThat(this.context.getBeanNamesForType(HandlerMapping.class).length) .isEqualTo(7); } @Test public void resourceHandlerMapping() throws Exception { load(); Map<String, List<Resource>> mappingLocations = getResourceMappingLocations(); assertThat(mappingLocations.get("/**")).hasSize(5); assertThat(mappingLocations.get("/webjars/**")).hasSize(1); assertThat(mappingLocations.get("/webjars/**").get(0)) .isEqualTo(new ClassPathResource("/META-INF/resources/webjars/")); assertThat(getResourceResolvers("/webjars/**")).hasSize(1); assertThat(getResourceTransformers("/webjars/**")).hasSize(0); assertThat(getResourceResolvers("/**")).hasSize(1); assertThat(getResourceTransformers("/**")).hasSize(0); } @Test public void customResourceHandlerMapping() throws Exception { load("spring.mvc.static-path-pattern:/static/**"); Map<String, List<Resource>> mappingLocations = getResourceMappingLocations(); assertThat(mappingLocations.get("/static/**")).hasSize(5); assertThat(getResourceResolvers("/static/**")).hasSize(1); } @Test public void resourceHandlerMappingOverrideWebjars() throws Exception { load(WebJars.class); Map<String, List<Resource>> mappingLocations = getResourceMappingLocations(); assertThat(mappingLocations.get("/webjars/**")).hasSize(1); assertThat(mappingLocations.get("/webjars/**").get(0)) .isEqualTo(new ClassPathResource("/foo/")); } @Test public void resourceHandlerMappingOverrideAll() throws Exception { load(AllResources.class); Map<String, List<Resource>> mappingLocations = getResourceMappingLocations(); assertThat(mappingLocations.get("/**")).hasSize(1); assertThat(mappingLocations.get("/**").get(0)) .isEqualTo(new ClassPathResource("/foo/")); } @Test public void resourceHandlerMappingDisabled() throws Exception { load("spring.resources.add-mappings:false"); Map<String, List<Resource>> mappingLocations = getResourceMappingLocations(); assertThat(mappingLocations.size()).isEqualTo(0); } @Test public void resourceHandlerChainEnabled() throws Exception { load("spring.resources.chain.enabled:true"); assertThat(getResourceResolvers("/webjars/**")).hasSize(2); assertThat(getResourceTransformers("/webjars/**")).hasSize(1); assertThat(getResourceResolvers("/**")).extractingResultOf("getClass") .containsOnly(CachingResourceResolver.class, PathResourceResolver.class); assertThat(getResourceTransformers("/**")).extractingResultOf("getClass") .containsOnly(CachingResourceTransformer.class); } @Test public void resourceHandlerFixedStrategyEnabled() throws Exception { load("spring.resources.chain.strategy.fixed.enabled:true", "spring.resources.chain.strategy.fixed.version:test", "spring.resources.chain.strategy.fixed.paths:/**/*.js"); assertThat(getResourceResolvers("/webjars/**")).hasSize(3); assertThat(getResourceTransformers("/webjars/**")).hasSize(2); assertThat(getResourceResolvers("/**")).extractingResultOf("getClass") .containsOnly(CachingResourceResolver.class, VersionResourceResolver.class, PathResourceResolver.class); assertThat(getResourceTransformers("/**")).extractingResultOf("getClass") .containsOnly(CachingResourceTransformer.class, CssLinkResourceTransformer.class); VersionResourceResolver resolver = (VersionResourceResolver) getResourceResolvers( "/**").get(1); assertThat(resolver.getStrategyMap().get("/**/*.js")) .isInstanceOf(FixedVersionStrategy.class); } @Test public void resourceHandlerContentStrategyEnabled() throws Exception { load("spring.resources.chain.strategy.content.enabled:true", "spring.resources.chain.strategy.content.paths:/**,/*.png"); assertThat(getResourceResolvers("/webjars/**")).hasSize(3); assertThat(getResourceTransformers("/webjars/**")).hasSize(2); assertThat(getResourceResolvers("/**")).extractingResultOf("getClass") .containsOnly(CachingResourceResolver.class, VersionResourceResolver.class, PathResourceResolver.class); assertThat(getResourceTransformers("/**")).extractingResultOf("getClass") .containsOnly(CachingResourceTransformer.class, CssLinkResourceTransformer.class); VersionResourceResolver resolver = (VersionResourceResolver) getResourceResolvers( "/**").get(1); assertThat(resolver.getStrategyMap().get("/*.png")) .isInstanceOf(ContentVersionStrategy.class); } @Test public void resourceHandlerChainCustomized() throws Exception { load("spring.resources.chain.enabled:true", "spring.resources.chain.cache:false", "spring.resources.chain.strategy.content.enabled:true", "spring.resources.chain.strategy.content.paths:/**,/*.png", "spring.resources.chain.strategy.fixed.enabled:true", "spring.resources.chain.strategy.fixed.version:test", "spring.resources.chain.strategy.fixed.paths:/**/*.js", "spring.resources.chain.html-application-cache:true", "spring.resources.chain.gzipped:true"); assertThat(getResourceResolvers("/webjars/**")).hasSize(3); assertThat(getResourceTransformers("/webjars/**")).hasSize(2); assertThat(getResourceResolvers("/**")).extractingResultOf("getClass") .containsOnly(VersionResourceResolver.class, GzipResourceResolver.class, PathResourceResolver.class); assertThat(getResourceTransformers("/**")).extractingResultOf("getClass") .containsOnly(CssLinkResourceTransformer.class, AppCacheManifestTransformer.class); VersionResourceResolver resolver = (VersionResourceResolver) getResourceResolvers( "/**").get(0); assertThat(resolver.getStrategyMap().get("/*.png")) .isInstanceOf(ContentVersionStrategy.class); assertThat(resolver.getStrategyMap().get("/**/*.js")) .isInstanceOf(FixedVersionStrategy.class); } @Test public void noLocaleResolver() throws Exception { load(AllResources.class); this.thrown.expect(NoSuchBeanDefinitionException.class); this.context.getBean(LocaleResolver.class); } @Test public void overrideLocale() throws Exception { load(AllResources.class, "spring.mvc.locale:en_UK", "spring.mvc.locale-resolver=fixed"); // mock request and set user preferred locale MockHttpServletRequest request = new MockHttpServletRequest(); request.addPreferredLocale(StringUtils.parseLocaleString("nl_NL")); request.addHeader(HttpHeaders.ACCEPT_LANGUAGE, "nl_NL"); LocaleResolver localeResolver = this.context.getBean(LocaleResolver.class); assertThat(localeResolver).isInstanceOf(FixedLocaleResolver.class); Locale locale = localeResolver.resolveLocale(request); // test locale resolver uses fixed locale and not user preferred locale assertThat(locale.toString()).isEqualTo("en_UK"); } @Test public void useAcceptHeaderLocale() { load(AllResources.class, "spring.mvc.locale:en_UK"); // mock request and set user preferred locale MockHttpServletRequest request = new MockHttpServletRequest(); request.addPreferredLocale(StringUtils.parseLocaleString("nl_NL")); request.addHeader(HttpHeaders.ACCEPT_LANGUAGE, "nl_NL"); LocaleResolver localeResolver = this.context.getBean(LocaleResolver.class); assertThat(localeResolver).isInstanceOf(AcceptHeaderLocaleResolver.class); Locale locale = localeResolver.resolveLocale(request); // test locale resolver uses user preferred locale assertThat(locale.toString()).isEqualTo("nl_NL"); } @Test public void useDefaultLocaleIfAcceptHeaderNoSet() { load(AllResources.class, "spring.mvc.locale:en_UK"); // mock request and set user preferred locale MockHttpServletRequest request = new MockHttpServletRequest(); LocaleResolver localeResolver = this.context.getBean(LocaleResolver.class); assertThat(localeResolver).isInstanceOf(AcceptHeaderLocaleResolver.class); Locale locale = localeResolver.resolveLocale(request); // test locale resolver uses default locale if no header is set assertThat(locale.toString()).isEqualTo("en_UK"); } @Test public void noDateFormat() throws Exception { load(AllResources.class); FormattingConversionService cs = this.context .getBean(FormattingConversionService.class); Date date = new DateTime(1988, 6, 25, 20, 30).toDate(); // formatting cs should use simple toString() assertThat(cs.convert(date, String.class)).isEqualTo(date.toString()); } @Test public void overrideDateFormat() throws Exception { load(AllResources.class, "spring.mvc.date-format:dd*MM*yyyy"); FormattingConversionService cs = this.context .getBean(FormattingConversionService.class); Date date = new DateTime(1988, 6, 25, 20, 30).toDate(); assertThat(cs.convert(date, String.class)).isEqualTo("25*06*1988"); } @Test public void noMessageCodesResolver() throws Exception { load(AllResources.class); assertThat(this.context.getBean(WebMvcAutoConfigurationAdapter.class) .getMessageCodesResolver()).isNull(); } @Test public void overrideMessageCodesFormat() throws Exception { load(AllResources.class, "spring.mvc.messageCodesResolverFormat:POSTFIX_ERROR_CODE"); assertThat(this.context.getBean(WebMvcAutoConfigurationAdapter.class) .getMessageCodesResolver()).isNotNull(); } protected Map<String, List<Resource>> getFaviconMappingLocations() throws IllegalAccessException { HandlerMapping mapping = (HandlerMapping) this.context .getBean("faviconHandlerMapping"); return getMappingLocations(mapping); } protected Map<String, List<Resource>> getResourceMappingLocations() throws IllegalAccessException { HandlerMapping mapping = (HandlerMapping) this.context .getBean("resourceHandlerMapping"); return getMappingLocations(mapping); } protected List<ResourceResolver> getResourceResolvers(String mapping) { SimpleUrlHandlerMapping handler = (SimpleUrlHandlerMapping) this.context .getBean("resourceHandlerMapping"); ResourceHttpRequestHandler resourceHandler = (ResourceHttpRequestHandler) handler .getHandlerMap().get(mapping); return resourceHandler.getResourceResolvers(); } protected List<ResourceTransformer> getResourceTransformers(String mapping) { SimpleUrlHandlerMapping handler = (SimpleUrlHandlerMapping) this.context .getBean("resourceHandlerMapping"); ResourceHttpRequestHandler resourceHandler = (ResourceHttpRequestHandler) handler .getHandlerMap().get(mapping); return resourceHandler.getResourceTransformers(); } @SuppressWarnings("unchecked") protected Map<String, List<Resource>> getMappingLocations(HandlerMapping mapping) throws IllegalAccessException { Map<String, List<Resource>> mappingLocations = new LinkedHashMap<>(); if (mapping instanceof SimpleUrlHandlerMapping) { Field locationsField = ReflectionUtils .findField(ResourceHttpRequestHandler.class, "locations"); locationsField.setAccessible(true); for (Map.Entry<String, Object> entry : ((SimpleUrlHandlerMapping) mapping) .getHandlerMap().entrySet()) { ResourceHttpRequestHandler handler = (ResourceHttpRequestHandler) entry .getValue(); mappingLocations.put(entry.getKey(), (List<Resource>) locationsField.get(handler)); } } return mappingLocations; } @Test public void ignoreDefaultModelOnRedirectIsTrue() throws Exception { load(); RequestMappingHandlerAdapter adapter = this.context .getBean(RequestMappingHandlerAdapter.class); assertThat(adapter).extracting("ignoreDefaultModelOnRedirect") .containsExactly(true); } @Test public void overrideIgnoreDefaultModelOnRedirect() throws Exception { this.context = new AnnotationConfigServletWebServerApplicationContext(); EnvironmentTestUtils.addEnvironment(this.context, "spring.mvc.ignore-default-model-on-redirect:false"); this.context.register(Config.class, WebMvcAutoConfiguration.class, HttpMessageConvertersAutoConfiguration.class, PropertyPlaceholderAutoConfiguration.class); this.context.refresh(); RequestMappingHandlerAdapter adapter = this.context .getBean(RequestMappingHandlerAdapter.class); assertThat(adapter).extracting("ignoreDefaultModelOnRedirect") .containsExactly(false); } @Test public void customViewResolver() throws Exception { load(CustomViewResolver.class); assertThat(this.context.getBean("viewResolver")) .isInstanceOf(MyViewResolver.class); } @Test public void customContentNegotiatingViewResolver() throws Exception { load(CustomContentNegotiatingViewResolver.class); Map<String, ContentNegotiatingViewResolver> beans = this.context .getBeansOfType(ContentNegotiatingViewResolver.class); assertThat(beans.size()).isEqualTo(1); assertThat(beans.keySet().iterator().next()).isEqualTo("myViewResolver"); } @Test public void faviconMapping() throws IllegalAccessException { load(); assertThat(this.context.getBeansOfType(ResourceHttpRequestHandler.class) .get("faviconRequestHandler")).isNotNull(); assertThat(this.context.getBeansOfType(SimpleUrlHandlerMapping.class) .get("faviconHandlerMapping")).isNotNull(); Map<String, List<Resource>> mappingLocations = getFaviconMappingLocations(); assertThat(mappingLocations.get("/**/favicon.ico")).hasSize(6); } @Test public void faviconMappingUsesStaticLocations() throws IllegalAccessException { load("spring.resources.static-locations=classpath:/static"); Map<String, List<Resource>> mappingLocations = getFaviconMappingLocations(); assertThat(mappingLocations.get("/**/favicon.ico")).hasSize(2); } @Test public void faviconMappingDisabled() throws IllegalAccessException { load("spring.mvc.favicon.enabled:false"); assertThat(this.context.getBeansOfType(ResourceHttpRequestHandler.class) .get("faviconRequestHandler")).isNull(); assertThat(this.context.getBeansOfType(SimpleUrlHandlerMapping.class) .get("faviconHandlerMapping")).isNull(); } @Test public void defaultAsyncRequestTimeout() throws Exception { load(); RequestMappingHandlerAdapter adapter = this.context .getBean(RequestMappingHandlerAdapter.class); assertThat(ReflectionTestUtils.getField(adapter, "asyncRequestTimeout")).isNull(); } @Test public void customAsyncRequestTimeout() throws Exception { load("spring.mvc.async.request-timeout:123456"); RequestMappingHandlerAdapter adapter = this.context .getBean(RequestMappingHandlerAdapter.class); Object actual = ReflectionTestUtils.getField(adapter, "asyncRequestTimeout"); assertThat(actual).isEqualTo(123456L); } @Test public void customMediaTypes() throws Exception { load("spring.mvc.mediaTypes.yaml:text/yaml"); RequestMappingHandlerAdapter adapter = this.context .getBean(RequestMappingHandlerAdapter.class); ContentNegotiationManager actual = (ContentNegotiationManager) ReflectionTestUtils .getField(adapter, "contentNegotiationManager"); assertThat(actual.getAllFileExtensions().contains("yaml")).isTrue(); } @Test public void httpPutFormContentFilterIsAutoConfigured() { load(); assertThat(this.context.getBeansOfType(OrderedHttpPutFormContentFilter.class)) .hasSize(1); } @Test public void httpPutFormContentFilterCanBeOverridden() { load(CustomHttpPutFormContentFilter.class); assertThat(this.context.getBeansOfType(OrderedHttpPutFormContentFilter.class)) .hasSize(0); assertThat(this.context.getBeansOfType(HttpPutFormContentFilter.class)) .hasSize(1); } @Test public void httpPutFormContentFilterCanBeDisabled() throws Exception { load((Class<?>) null, "spring.mvc.formcontent.putfilter.enabled=false"); assertThat(this.context.getBeansOfType(HttpPutFormContentFilter.class)).isEmpty(); } @Test public void customConfigurableWebBindingInitializer() { load(CustomConfigurableWebBindingInitializer.class); assertThat(this.context.getBean(RequestMappingHandlerAdapter.class) .getWebBindingInitializer()) .isInstanceOf(CustomWebBindingInitializer.class); } @Test public void customRequestMappingHandlerMapping() { load(CustomRequestMappingHandlerMapping.class); assertThat(this.context.getBean(RequestMappingHandlerMapping.class)) .isInstanceOf(MyRequestMappingHandlerMapping.class); } @Test public void customRequestMappingHandlerAdapter() { load(CustomRequestMappingHandlerAdapter.class); assertThat(this.context.getBean(RequestMappingHandlerAdapter.class)) .isInstanceOf(MyRequestMappingHandlerAdapter.class); } @Test public void multipleWebMvcRegistrations() { load(MultipleWebMvcRegistrations.class); assertThat(this.context.getBean(RequestMappingHandlerMapping.class)) .isNotInstanceOf(MyRequestMappingHandlerMapping.class); assertThat(this.context.getBean(RequestMappingHandlerAdapter.class)) .isNotInstanceOf(MyRequestMappingHandlerAdapter.class); } @Test public void defaultLogResolvedException() { load(); testLogResolvedExceptionCustomization(false); } @Test public void customLogResolvedException() { load("spring.mvc.log-resolved-exception:true"); testLogResolvedExceptionCustomization(true); } @Test public void welcomePageMappingProducesNotFoundResponseWhenThereIsNoWelcomePage() throws Exception { load("spring.resources.static-locations:classpath:/no-welcome-page/"); assertThat(this.context.getBeansOfType(WelcomePageHandlerMapping.class)) .hasSize(1); MockMvcBuilders.webAppContextSetup(this.context).build() .perform(get("/").accept(MediaType.TEXT_HTML)) .andExpect(status().isNotFound()); } @Test public void welcomePageRootHandlerIsNotRegisteredWhenStaticPathPatternIsNotSlashStarStar() { load("spring.resources.static-locations:classpath:/welcome-page/", "spring.mvc.static-path-pattern:/foo/**"); WelcomePageHandlerMapping welcomePageHandlerMapping = this.context .getBean(WelcomePageHandlerMapping.class); assertThat(welcomePageHandlerMapping.getRootHandler()).isNull(); } @Test public void welcomePageMappingHandlesRequestsThatAcceptTextHtml() throws Exception { load("spring.resources.static-locations:classpath:/welcome-page/"); assertThat(this.context.getBeansOfType(WelcomePageHandlerMapping.class)) .hasSize(1); MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(this.context).build(); mockMvc.perform(get("/").accept(MediaType.TEXT_HTML)).andExpect(status().isOk()) .andExpect(forwardedUrl("index.html")); mockMvc.perform(get("/").accept("*/*")).andExpect(status().isOk()) .andExpect(forwardedUrl("index.html")); } @Test public void welcomePageMappingDoesNotHandleRequestsThatDoNotAcceptTextHtml() throws Exception { load("spring.resources.static-locations:classpath:/welcome-page/"); assertThat(this.context.getBeansOfType(WelcomePageHandlerMapping.class)) .hasSize(1); MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(this.context).build(); mockMvc.perform(get("/").accept(MediaType.APPLICATION_JSON)) .andExpect(status().isNotFound()); } @Test public void welcomePageMappingHandlesRequestsWithNoAcceptHeader() throws Exception { load("spring.resources.static-locations:classpath:/welcome-page/"); assertThat(this.context.getBeansOfType(WelcomePageHandlerMapping.class)) .hasSize(1); MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(this.context).build(); mockMvc.perform(get("/")).andExpect(status().isOk()) .andExpect(forwardedUrl("index.html")); } @Test public void welcomePageMappingHandlesRequestsWithEmptyAcceptHeader() throws Exception { load("spring.resources.static-locations:classpath:/welcome-page/"); assertThat(this.context.getBeansOfType(WelcomePageHandlerMapping.class)) .hasSize(1); MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(this.context).build(); mockMvc.perform(get("/").header(HttpHeaders.ACCEPT, "")) .andExpect(status().isOk()).andExpect(forwardedUrl("index.html")); } @Test public void welcomePageMappingWorksWithNoTrailingSlashOnResourceLocation() throws Exception { load("spring.resources.static-locations:classpath:/welcome-page"); assertThat(this.context.getBeansOfType(WelcomePageHandlerMapping.class)) .hasSize(1); MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(this.context).build(); mockMvc.perform(get("/").accept(MediaType.TEXT_HTML)).andExpect(status().isOk()) .andExpect(forwardedUrl("index.html")); } private void testLogResolvedExceptionCustomization(final boolean expected) { HandlerExceptionResolver exceptionResolver = this.context .getBean(HandlerExceptionResolver.class); assertThat(exceptionResolver) .isInstanceOf(HandlerExceptionResolverComposite.class); List<HandlerExceptionResolver> delegates = ((HandlerExceptionResolverComposite) exceptionResolver) .getExceptionResolvers(); for (HandlerExceptionResolver delegate : delegates) { if (delegate instanceof AbstractHandlerMethodAdapter) { assertThat( new DirectFieldAccessor(delegate).getPropertyValue("warnLogger")) .is(new Condition<Object>() { @Override public boolean matches(Object value) { return (expected ? value != null : value == null); } }); } } } @Test public void validatorWhenNoValidatorShouldUseDefault() { load(null, new Class<?>[] { ValidationAutoConfiguration.class }); assertThat(this.context.getBeansOfType(ValidatorFactory.class)).isEmpty(); assertThat(this.context.getBeansOfType(javax.validation.Validator.class)) .isEmpty(); String[] springValidatorBeans = this.context.getBeanNamesForType(Validator.class); assertThat(springValidatorBeans).containsExactly("mvcValidator"); } @Test public void validatorWhenNoCustomizationShouldUseAutoConfigured() { load(); String[] jsrValidatorBeans = this.context .getBeanNamesForType(javax.validation.Validator.class); String[] springValidatorBeans = this.context.getBeanNamesForType(Validator.class); assertThat(jsrValidatorBeans).containsExactly("defaultValidator"); assertThat(springValidatorBeans).containsExactly("defaultValidator", "mvcValidator"); Validator validator = this.context.getBean("mvcValidator", Validator.class); assertThat(validator).isInstanceOf(ValidatorAdapter.class); Object defaultValidator = this.context.getBean("defaultValidator"); assertThat(((ValidatorAdapter) validator).getTarget()).isSameAs(defaultValidator); // Primary Spring validator is the one use by MVC behind the scenes assertThat(this.context.getBean(Validator.class)).isEqualTo(defaultValidator); } @Test public void validatorWithConfigurerShouldUseSpringValidator() { load(MvcValidator.class, new Class<?>[] { ValidationAutoConfiguration.class }); assertThat(this.context.getBeansOfType(ValidatorFactory.class)).isEmpty(); assertThat(this.context.getBeansOfType(javax.validation.Validator.class)) .isEmpty(); String[] springValidatorBeans = this.context.getBeanNamesForType(Validator.class); assertThat(springValidatorBeans).containsExactly("mvcValidator"); assertThat(this.context.getBean("mvcValidator")) .isSameAs(this.context.getBean(MvcValidator.class).validator); } @Test public void validatorWithConfigurerDoesNotExposeJsr303() { load(MvcJsr303Validator.class, new Class<?>[] { ValidationAutoConfiguration.class }); assertThat(this.context.getBeansOfType(ValidatorFactory.class)).isEmpty(); assertThat(this.context.getBeansOfType(javax.validation.Validator.class)) .isEmpty(); String[] springValidatorBeans = this.context.getBeanNamesForType(Validator.class); assertThat(springValidatorBeans).containsExactly("mvcValidator"); Validator validator = this.context.getBean("mvcValidator", Validator.class); assertThat(validator).isInstanceOf(ValidatorAdapter.class); assertThat(((ValidatorAdapter) validator).getTarget()) .isSameAs(this.context.getBean(MvcJsr303Validator.class).validator); } @Test public void validatorWithConfigurerTakesPrecedence() { load(MvcValidator.class); assertThat(this.context.getBeansOfType(ValidatorFactory.class)).hasSize(1); assertThat(this.context.getBeansOfType(javax.validation.Validator.class)) .hasSize(1); String[] springValidatorBeans = this.context.getBeanNamesForType(Validator.class); assertThat(springValidatorBeans).containsExactly("defaultValidator", "mvcValidator"); assertThat(this.context.getBean("mvcValidator")) .isSameAs(this.context.getBean(MvcValidator.class).validator); // Primary Spring validator is the auto-configured one as the MVC one has been // customized via a WebMvcConfigurer assertThat(this.context.getBean(Validator.class)) .isEqualTo(this.context.getBean("defaultValidator")); } @Test public void validatorWithCustomSpringValidatorIgnored() { load(CustomSpringValidator.class); String[] jsrValidatorBeans = this.context .getBeanNamesForType(javax.validation.Validator.class); String[] springValidatorBeans = this.context.getBeanNamesForType(Validator.class); assertThat(jsrValidatorBeans).containsExactly("defaultValidator"); assertThat(springValidatorBeans).containsExactly( "customSpringValidator", "defaultValidator", "mvcValidator"); Validator validator = this.context.getBean("mvcValidator", Validator.class); assertThat(validator).isInstanceOf(ValidatorAdapter.class); Object defaultValidator = this.context.getBean("defaultValidator"); assertThat(((ValidatorAdapter) validator).getTarget()) .isSameAs(defaultValidator); // Primary Spring validator is the one use by MVC behind the scenes assertThat(this.context.getBean(Validator.class)).isEqualTo(defaultValidator); } @Test public void validatorWithCustomJsr303ValidatorExposedAsSpringValidator() { load(CustomJsr303Validator.class); assertThat(this.context.getBeansOfType(ValidatorFactory.class)).isEmpty(); String[] jsrValidatorBeans = this.context .getBeanNamesForType(javax.validation.Validator.class); String[] springValidatorBeans = this.context.getBeanNamesForType(Validator.class); assertThat(jsrValidatorBeans).containsExactly("customJsr303Validator"); assertThat(springValidatorBeans).containsExactly("mvcValidator"); Validator validator = this.context.getBean(Validator.class); assertThat(validator).isInstanceOf(ValidatorAdapter.class); Validator target = ((ValidatorAdapter) validator).getTarget(); assertThat(new DirectFieldAccessor(target).getPropertyValue("targetValidator")) .isSameAs(this.context.getBean("customJsr303Validator")); } private void load(Class<?> config, String... environment) { load(config, null, environment); } private void load(Class<?> config, Class<?>[] exclude, String... environment) { this.context = new AnnotationConfigServletWebServerApplicationContext(); EnvironmentTestUtils.addEnvironment(this.context, environment); List<Class<?>> configClasses = new ArrayList<>(); if (config != null) { configClasses.add(config); } configClasses.addAll(Arrays.asList(Config.class, ValidationAutoConfiguration.class, WebMvcAutoConfiguration.class, HttpMessageConvertersAutoConfiguration.class, PropertyPlaceholderAutoConfiguration.class)); if (!ObjectUtils.isEmpty(exclude)) { configClasses.removeAll(Arrays.asList(exclude)); } this.context.register(configClasses.toArray(new Class<?>[configClasses.size()])); this.context.refresh(); } private void load(String... environment) { load(null, environment); } @Configuration protected static class ViewConfig { @Bean public View jsonView() { return new AbstractView() { @Override protected void renderMergedOutputModel(Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws Exception { response.getOutputStream().write("Hello World".getBytes()); } }; } } @Configuration protected static class WebJars implements WebMvcConfigurer { @Override public void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler("/webjars/**") .addResourceLocations("classpath:/foo/"); } } @Configuration protected static class AllResources implements WebMvcConfigurer { @Override public void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler("/**").addResourceLocations("classpath:/foo/"); } } @Configuration public static class Config { @Bean public ServletWebServerFactory webServerFactory() { return webServerFactory; } @Bean public WebServerFactoryCustomizerBeanPostProcessor ServletWebServerCustomizerBeanPostProcessor() { return new WebServerFactoryCustomizerBeanPostProcessor(); } } @Configuration public static class CustomViewResolver { @Bean public ViewResolver viewResolver() { return new MyViewResolver(); } } @Configuration public static class CustomContentNegotiatingViewResolver { @Bean public ContentNegotiatingViewResolver myViewResolver() { return new ContentNegotiatingViewResolver(); } } private static class MyViewResolver implements ViewResolver { @Override public View resolveViewName(String viewName, Locale locale) throws Exception { return null; } } @Configuration static class CustomConfigurableWebBindingInitializer { @Bean public ConfigurableWebBindingInitializer customConfigurableWebBindingInitializer() { return new CustomWebBindingInitializer(); } } private static class CustomWebBindingInitializer extends ConfigurableWebBindingInitializer { } @Configuration static class CustomHttpPutFormContentFilter { @Bean public HttpPutFormContentFilter customHttpPutFormContentFilter() { return new HttpPutFormContentFilter(); } } @Configuration static class CustomRequestMappingHandlerMapping { @Bean public WebMvcRegistrationsAdapter webMvcRegistrationsHandlerMapping() { return new WebMvcRegistrationsAdapter() { @Override public RequestMappingHandlerMapping getRequestMappingHandlerMapping() { return new MyRequestMappingHandlerMapping(); } }; } } private static class MyRequestMappingHandlerMapping extends RequestMappingHandlerMapping { } @Configuration static class CustomRequestMappingHandlerAdapter { @Bean public WebMvcRegistrationsAdapter webMvcRegistrationsHandlerAdapter() { return new WebMvcRegistrationsAdapter() { @Override public RequestMappingHandlerAdapter getRequestMappingHandlerAdapter() { return new MyRequestMappingHandlerAdapter(); } }; } } private static class MyRequestMappingHandlerAdapter extends RequestMappingHandlerAdapter { } @Configuration @Import({ CustomRequestMappingHandlerMapping.class, CustomRequestMappingHandlerAdapter.class }) static class MultipleWebMvcRegistrations { } @Configuration protected static class MvcValidator implements WebMvcConfigurer { private final Validator validator = mock(Validator.class); @Override public Validator getValidator() { return this.validator; } } @Configuration protected static class MvcJsr303Validator implements WebMvcConfigurer { private final LocalValidatorFactoryBean validator = new LocalValidatorFactoryBean(); @Override public Validator getValidator() { return this.validator; } } @Configuration static class CustomJsr303Validator { @Bean public javax.validation.Validator customJsr303Validator() { return mock(javax.validation.Validator.class); } } @Configuration static class CustomSpringValidator { @Bean public Validator customSpringValidator() { return mock(Validator.class); } } }