DATACMNS-993 - Clean up class loader hacking in tests.

ClassLoaderRule with @ClassLoaderConfiguration allows easy creation FilteringClassLoader. Changed usage pattern of ClassUtils.isPresent(…) in order to ease testing. Copied the ShadowingClassloader from SpringFramework to get a constructor that doesn't shadow anything by default.

Original pull request: #202.
This commit is contained in:
Jens Schauder
2017-02-14 20:24:44 +01:00
committed by Oliver Gierke
parent 562b6665a3
commit 65734739bd
9 changed files with 492 additions and 86 deletions

View File

@@ -24,7 +24,7 @@ import org.springframework.web.context.support.AnnotationConfigWebApplicationCon
/**
* Helper methods for web integration testing.
*
*
* @author Oliver Gierke
*/
public class WebTestUtils {
@@ -41,13 +41,28 @@ public class WebTestUtils {
/**
* Creates a {@link WebApplicationContext} from the given configuration classes.
*
*
* @param configClasses
* @return
*/
public static WebApplicationContext createApplicationContext(Class<?>... configClasses) {
return createApplicationContext(null, configClasses);
}
/**
* Creates a {@link WebApplicationContext} from the given configuration classes.
*
* @param classLoader gets set as ClassLoader in the context
* @param configClasses
* @return
*/
public static WebApplicationContext createApplicationContext(ClassLoader classLoader, Class<?>... configClasses) {
AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
if (classLoader != null) {
context.setClassLoader(classLoader);
}
context.setServletContext(new MockServletContext());
for (Class<?> configClass : configClasses) {

View File

@@ -20,56 +20,62 @@ import static org.springframework.test.web.servlet.request.MockMvcRequestBuilder
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.junit.After;
import org.hamcrest.Matcher;
import org.junit.Rule;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.convert.ConversionService;
import org.springframework.data.classloadersupport.ClassLoaderConfiguration;
import org.springframework.data.classloadersupport.ClassLoaderRule;
import org.springframework.data.geo.Distance;
import org.springframework.data.geo.Point;
import org.springframework.data.web.PageableHandlerMethodArgumentResolver;
import org.springframework.data.web.PagedResourcesAssemblerArgumentResolver;
import org.springframework.data.web.SortHandlerMethodArgumentResolver;
import org.springframework.data.web.WebTestUtils;
import org.springframework.data.web.config.EnableSpringDataWebSupport.SpringDataWebConfigurationImportSelector;
import org.springframework.hateoas.Link;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.util.ReflectionUtils;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter;
import org.springframework.web.util.UriComponentsBuilder;
/**
* Integration tests for {@link EnableSpringDataWebSupport}.
*
*
* @author Oliver Gierke
* @author Jens Schauder
*/
@ClassLoaderConfiguration(shadowPackage = {
WebMvcConfigurationSupport.class,
EnableSpringDataWebSupport.class,
EnableSpringDataWebSupportIntegrationTests.SampleConfig.class})
public class EnableSpringDataWebSupportIntegrationTests {
private static final String HATEOAS = "HATEOAS_PRESENT";
private static final String JACKSON = "JACKSON_PRESENT";
@Configuration
@EnableWebMvc
@EnableSpringDataWebSupport
static class SampleConfig {
public @Bean SampleController controller() {
public
@Bean
SampleController controller() {
return new SampleController();
}
}
@After
public void tearDown() {
reEnable(HATEOAS);
reEnable(JACKSON);
}
@Rule public ClassLoaderRule classLoaderRule = new ClassLoaderRule();
@Test // DATACMNS-330
public void registersBasicBeanDefinitions() throws Exception {
@@ -94,11 +100,11 @@ public class EnableSpringDataWebSupportIntegrationTests {
}
@Test // DATACMNS-330
@ClassLoaderConfiguration(hidePackage = Link.class)
public void doesNotRegisterHateoasSpecificComponentsIfHateoasNotPresent() throws Exception {
hide(HATEOAS);
ApplicationContext context = WebTestUtils.createApplicationContext(classLoaderRule.classLoader, SampleConfig.class);
ApplicationContext context = WebTestUtils.createApplicationContext(SampleConfig.class);
List<String> names = Arrays.asList(context.getBeanDefinitionNames());
assertThat(names).contains("pageableResolver", "sortResolver");
@@ -115,11 +121,11 @@ public class EnableSpringDataWebSupportIntegrationTests {
}
@Test // DATACMNS-475
@ClassLoaderConfiguration(hidePackage = com.fasterxml.jackson.databind.ObjectMapper.class)
public void doesNotRegisterJacksonSpecificComponentsIfJacksonNotPresent() throws Exception {
hide(JACKSON);
ApplicationContext context = WebTestUtils.createApplicationContext(classLoaderRule.classLoader, SampleConfig.class);
ApplicationContext context = WebTestUtils.createApplicationContext(SampleConfig.class);
List<String> names = Arrays.asList(context.getBeanDefinitionNames());
assertThat(names).doesNotContain("jacksonGeoModule");
@@ -173,18 +179,4 @@ public class EnableSpringDataWebSupportIntegrationTests {
Arrays.asList(resolverTypes).forEach(type -> assertThat(resolvers).hasAtLeastOneElementOfType(type));
}
private static void hide(String module) throws Exception {
Field field = ReflectionUtils.findField(SpringDataWebConfigurationImportSelector.class, module);
ReflectionUtils.makeAccessible(field);
ReflectionUtils.setField(field, null, false);
}
private static void reEnable(String module) {
Field field = ReflectionUtils.findField(SpringDataWebConfigurationImportSelector.class, module);
ReflectionUtils.makeAccessible(field);
ReflectionUtils.setField(field, null, true);
}
}

View File

@@ -18,20 +18,17 @@ package org.springframework.data.web.config;
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.List;
import org.hamcrest.Matcher;
import org.junit.Rule;
import org.junit.Test;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.ObjectFactory;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.core.convert.ConversionService;
import org.springframework.data.classloadersupport.ClassLoaderConfiguration;
import org.springframework.data.classloadersupport.ClassLoaderRule;
import org.springframework.data.web.ProjectingJackson2HttpMessageConverter;
import org.springframework.data.web.XmlBeamHttpMessageConverter;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.instrument.classloading.ShadowingClassLoader;
/**
* Integration test for {@link SpringDataWebConfiguration}.
@@ -42,32 +39,37 @@ import org.springframework.instrument.classloading.ShadowingClassLoader;
*/
public class SpringDataWebConfigurationIntegrationTests {
@Rule public ClassLoaderRule classLoader = new ClassLoaderRule();
@Test // DATACMNS-987
@ClassLoaderConfiguration(hidePackage = com.fasterxml.jackson.databind.ObjectMapper.class)
public void shouldNotLoadJacksonConverterWhenJacksonNotPresent() {
List<HttpMessageConverter<?>> converters = new ArrayList<HttpMessageConverter<?>>();
createConfigWithClassLoaderExcluding("com.fasterxml.jackson").extendMessageConverters(converters);
createConfigWithClassLoader().extendMessageConverters(converters);
assertThat(converters, not(hasItem(instanceWithClassName(ProjectingJackson2HttpMessageConverter.class))));
}
@Test // DATACMNS-987
@ClassLoaderConfiguration(hidePackage = com.jayway.jsonpath.DocumentContext.class)
public void shouldNotLoadJacksonConverterWhenJaywayNotPresent() {
List<HttpMessageConverter<?>> converters = new ArrayList<HttpMessageConverter<?>>();
createConfigWithClassLoaderExcluding("com.jayway").extendMessageConverters(converters);
createConfigWithClassLoader().extendMessageConverters(converters);
assertThat(converters, not(hasItem(instanceWithClassName(ProjectingJackson2HttpMessageConverter.class))));
}
@Test // DATACMNS-987
@ClassLoaderConfiguration(hidePackage = org.xmlbeam.ProjectionFactory.class)
public void shouldNotLoadXBeamConverterWhenXBeamNotPresent() throws Exception {
List<HttpMessageConverter<?>> converters = new ArrayList<HttpMessageConverter<?>>();
createConfigWithClassLoaderExcluding("org.xmlbeam").extendMessageConverters(converters);
createConfigWithClassLoader().extendMessageConverters(converters);
assertThat(converters, not(hasItem(instanceWithClassName(XmlBeamHttpMessageConverter.class))));
}
@@ -77,17 +79,17 @@ public class SpringDataWebConfigurationIntegrationTests {
List<HttpMessageConverter<?>> converters = new ArrayList<HttpMessageConverter<?>>();
createConfigWithClassLoaderExcluding("load.everything").extendMessageConverters(converters);
createConfigWithClassLoader().extendMessageConverters(converters);
assertThat(converters, hasItem(instanceWithClassName(XmlBeamHttpMessageConverter.class)));
assertThat(converters, hasItem(instanceWithClassName(ProjectingJackson2HttpMessageConverter.class)));
}
private static SpringDataWebConfiguration createConfigWithClassLoaderExcluding(String excludedClassNamePrefix) {
private SpringDataWebConfiguration createConfigWithClassLoader() {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(
SpringDataWebConfiguration.class);
context.setClassLoader(initClassLoader(excludedClassNamePrefix));
context.setClassLoader(classLoader.classLoader);
try {
return context.getBean(SpringDataWebConfiguration.class);
@@ -106,38 +108,4 @@ public class SpringDataWebConfigurationIntegrationTests {
private static <T> Matcher<T> instanceWithClassName(Class<T> expectedClass) {
return hasProperty("class", hasProperty("name", equalTo(expectedClass.getName())));
}
private static ClassLoader initClassLoader(final String excludedClassNamePrefix) {
return new ShadowingClassLoader(URLClassLoader.getSystemClassLoader()) {
@Override
public Class<?> loadClass(String name) throws ClassNotFoundException {
if (name.startsWith(excludedClassNamePrefix)) {
throw new ClassNotFoundException();
}
return super.loadClass(name);
}
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
if (name.startsWith(excludedClassNamePrefix)) {
throw new ClassNotFoundException();
}
return super.findClass(name);
}
};
}
public static class ObjectFactoryImpl implements ObjectFactory<ConversionService> {
@Override
public ConversionService getObject() throws BeansException {
return null;
}
}
}