Relocate projects to spring-boot-project

Move projects to better reflect the way that Spring Boot is released.

The following projects are under `spring-boot-project`:

  - `spring-boot`
  - `spring-boot-autoconfigure`
  - `spring-boot-tools`
  - `spring-boot-starters`
  - `spring-boot-actuator`
  - `spring-boot-actuator-autoconfigure`
  - `spring-boot-test`
  - `spring-boot-test-autoconfigure`
  - `spring-boot-devtools`
  - `spring-boot-cli`
  - `spring-boot-docs`

See gh-9316
This commit is contained in:
Phillip Webb
2017-09-19 14:29:46 -07:00
parent 0419d42b7c
commit 0ba4830b4f
4023 changed files with 0 additions and 0 deletions

View File

@@ -0,0 +1,45 @@
/*
* 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.test;
import org.junit.AssumptionViolatedException;
import org.springframework.boot.system.JavaVersion;
/**
* Provides utility methods that allow JUnit tests to {@link org.junit.Assume} certain
* conditions hold {@code true}. If the assumption fails, it means the test should be
* skipped.
*
* @author Stephane Nicoll
* @since 2.0.0
*/
public abstract class Assume {
/**
* Assume that the specified {@link JavaVersion} is the one currently available.
* @param version the expected Java version
* @throws AssumptionViolatedException if the assumption fails
*/
public static void javaVersion(JavaVersion version) {
JavaVersion current = JavaVersion.getJavaVersion();
org.junit.Assume.assumeTrue(
String.format("This test should run on %s (got %s)", version, current),
current == version);
}
}

View File

@@ -0,0 +1,47 @@
/*
* Copyright 2012-2016 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.test.context;
import org.springframework.boot.context.config.ConfigFileApplicationListener;
import org.springframework.context.ApplicationContextInitializer;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.test.context.ContextConfiguration;
/**
* {@link ApplicationContextInitializer} that can be used with the
* {@link ContextConfiguration#initializers()} to trigger loading of
* {@literal application.properties}.
*
* @author Phillip Webb
* @since 1.4.0
* @see ConfigFileApplicationListener
*/
public class ConfigFileApplicationContextInitializer
implements ApplicationContextInitializer<ConfigurableApplicationContext> {
@Override
public void initialize(final ConfigurableApplicationContext applicationContext) {
new ConfigFileApplicationListener() {
public void apply() {
addPropertySources(applicationContext.getEnvironment(),
applicationContext);
addPostProcessors(applicationContext);
}
}.apply();
}
}

View File

@@ -0,0 +1,43 @@
/*
* 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.test.context;
import java.util.Set;
import org.springframework.test.context.TestExecutionListener;
/**
* Callback interface trigger from {@link SpringBootTestContextBootstrapper} that can be
* used to post-process the list of default {@link TestExecutionListener} classes to be
* used by a test. Can be used to add or remove existing listener classes.
*
* @author Phillip Webb
* @since 1.4.1
* @see SpringBootTest
*/
@FunctionalInterface
public interface DefaultTestExecutionListenersPostProcessor {
/**
* Post process the list of default {@link TestExecutionListener} classes to be used.
* @param listeners the source listeners
* @return the actual listeners that should be used
*/
Set<Class<? extends TestExecutionListener>> postProcessDefaultTestExecutionListeners(
Set<Class<? extends TestExecutionListener>> listeners);
}

View File

@@ -0,0 +1,48 @@
/*
* 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.test.context;
import java.net.URL;
import java.net.URLClassLoader;
/**
* Test {@link URLClassLoader} that hides configurable classes.
*
* @author Stephane Nicoll
* @since 2.0.0
*/
public class HideClassesClassLoader extends URLClassLoader {
private final Class<?>[] hiddenClasses;
public HideClassesClassLoader(Class<?>... hiddenClasses) {
super(new URL[0], HideClassesClassLoader.class.getClassLoader());
this.hiddenClasses = hiddenClasses;
}
@Override
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException {
for (Class<?> hiddenClass : this.hiddenClasses) {
if (name.equals(hiddenClass.getName())) {
throw new ClassNotFoundException();
}
}
return super.loadClass(name, resolve);
}
}

View File

@@ -0,0 +1,54 @@
/*
* 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.test.context;
import java.net.URL;
import java.net.URLClassLoader;
/**
* Test {@link URLClassLoader} that hides configurable packages. No class in one of those
* packages or sub-packages are visible.
*
* @author Andy Wilkinson
* @author Stephane Nicoll
* @since 2.0.0
*/
public final class HidePackagesClassLoader extends URLClassLoader {
private final String[] hiddenPackages;
/**
* Create a new instance with the packages to hide.
* @param hiddenPackages the packages to hide
*/
public HidePackagesClassLoader(String... hiddenPackages) {
super(new URL[0], HidePackagesClassLoader.class.getClassLoader());
this.hiddenPackages = hiddenPackages;
}
@Override
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException {
for (String hiddenPackage : this.hiddenPackages) {
if (name.startsWith(hiddenPackage)) {
throw new ClassNotFoundException();
}
}
return super.loadClass(name, resolve);
}
}

View File

@@ -0,0 +1,410 @@
/*
* 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.test.context;
import java.lang.annotation.Annotation;
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Constructor;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.Set;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor;
import org.springframework.boot.context.annotation.DeterminableImports;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.AnnotatedBeanDefinitionReader;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.context.annotation.ImportSelector;
import org.springframework.context.support.AbstractApplicationContext;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.core.annotation.Order;
import org.springframework.core.style.ToStringCreator;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.core.type.StandardAnnotationMetadata;
import org.springframework.test.context.ContextCustomizer;
import org.springframework.test.context.MergedContextConfiguration;
import org.springframework.util.ReflectionUtils;
/**
* {@link ContextCustomizer} to allow {@code @Import} annotations to be used directly on
* test classes.
*
* @author Phillip Webb
* @author Andy Wilkinson
* @see ImportsContextCustomizerFactory
*/
class ImportsContextCustomizer implements ContextCustomizer {
static final String TEST_CLASS_ATTRIBUTE = "testClass";
private final Class<?> testClass;
private final ContextCustomizerKey key;
ImportsContextCustomizer(Class<?> testClass) {
this.testClass = testClass;
this.key = new ContextCustomizerKey(testClass);
}
@Override
public void customizeContext(ConfigurableApplicationContext context,
MergedContextConfiguration mergedContextConfiguration) {
BeanDefinitionRegistry registry = getBeanDefinitionRegistry(context);
AnnotatedBeanDefinitionReader reader = new AnnotatedBeanDefinitionReader(
registry);
registerCleanupPostProcessor(registry, reader);
registerImportsConfiguration(registry, reader);
}
private void registerCleanupPostProcessor(BeanDefinitionRegistry registry,
AnnotatedBeanDefinitionReader reader) {
BeanDefinition definition = registerBean(registry, reader,
ImportsCleanupPostProcessor.BEAN_NAME, ImportsCleanupPostProcessor.class);
definition.getConstructorArgumentValues().addIndexedArgumentValue(0,
this.testClass);
}
private void registerImportsConfiguration(BeanDefinitionRegistry registry,
AnnotatedBeanDefinitionReader reader) {
BeanDefinition definition = registerBean(registry, reader,
ImportsConfiguration.BEAN_NAME, ImportsConfiguration.class);
definition.setAttribute(TEST_CLASS_ATTRIBUTE, this.testClass);
}
private BeanDefinitionRegistry getBeanDefinitionRegistry(ApplicationContext context) {
if (context instanceof BeanDefinitionRegistry) {
return (BeanDefinitionRegistry) context;
}
if (context instanceof AbstractApplicationContext) {
return (BeanDefinitionRegistry) ((AbstractApplicationContext) context)
.getBeanFactory();
}
throw new IllegalStateException("Could not locate BeanDefinitionRegistry");
}
@SuppressWarnings("unchecked")
private BeanDefinition registerBean(BeanDefinitionRegistry registry,
AnnotatedBeanDefinitionReader reader, String beanName, Class<?> type) {
reader.registerBean(type, beanName);
BeanDefinition definition = registry.getBeanDefinition(beanName);
return definition;
}
@Override
public int hashCode() {
return this.key.hashCode();
}
@Override
public boolean equals(Object obj) {
if (obj == this) {
return true;
}
if (obj == null || obj.getClass() != getClass()) {
return false;
}
// ImportSelectors are flexible so the only safe cache key is the test class
ImportsContextCustomizer other = (ImportsContextCustomizer) obj;
return this.key.equals(other.key);
}
@Override
public String toString() {
return new ToStringCreator(this).append("key", this.key).toString();
}
/**
* {@link Configuration} registered to trigger the {@link ImportsSelector}.
*/
@Configuration
@Import(ImportsSelector.class)
static class ImportsConfiguration {
static final String BEAN_NAME = ImportsConfiguration.class.getName();
}
/**
* {@link ImportSelector} that returns the original test class so that direct
* {@code @Import} annotations are processed.
*/
static class ImportsSelector implements ImportSelector, BeanFactoryAware {
private static final String[] NO_IMPORTS = {};
private ConfigurableListableBeanFactory beanFactory;
@Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
this.beanFactory = (ConfigurableListableBeanFactory) beanFactory;
}
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
BeanDefinition definition = this.beanFactory
.getBeanDefinition(ImportsConfiguration.BEAN_NAME);
Object testClass = (definition == null ? null
: definition.getAttribute(TEST_CLASS_ATTRIBUTE));
return (testClass == null ? NO_IMPORTS
: new String[] { ((Class<?>) testClass).getName() });
}
}
/**
* {@link BeanDefinitionRegistryPostProcessor} to cleanup temporary configuration
* added to load imports.
*/
@Order(Ordered.LOWEST_PRECEDENCE)
static class ImportsCleanupPostProcessor
implements BeanDefinitionRegistryPostProcessor {
static final String BEAN_NAME = ImportsCleanupPostProcessor.class.getName();
private final Class<?> testClass;
ImportsCleanupPostProcessor(Class<?> testClass) {
this.testClass = testClass;
}
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory)
throws BeansException {
}
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry)
throws BeansException {
try {
String[] names = registry.getBeanDefinitionNames();
for (String name : names) {
BeanDefinition definition = registry.getBeanDefinition(name);
if (this.testClass.getName().equals(definition.getBeanClassName())) {
registry.removeBeanDefinition(name);
}
}
registry.removeBeanDefinition(ImportsConfiguration.BEAN_NAME);
}
catch (NoSuchBeanDefinitionException ex) {
}
}
}
/**
* The key used to ensure correct application context caching. Keys are generated
* based on <em>all</em> the annotations used with the test that aren't core Java or
* Kotlin annotations. We must use something broader than just {@link Import @Import}
* annotations since an {@code @Import} may use an {@link ImportSelector} which could
* make decisions based on anything available from {@link AnnotationMetadata}.
*/
static class ContextCustomizerKey {
private static final Class<?>[] NO_IMPORTS = {};
private static final Set<AnnotationFilter> annotationFilters;
static {
Set<AnnotationFilter> filters = new HashSet<>();
filters.add(new JavaLangAnnotationFilter());
filters.add(new KotlinAnnotationFilter());
filters.add(new SpockAnnotationFilter());
annotationFilters = Collections.unmodifiableSet(filters);
}
private final Set<Object> key;
ContextCustomizerKey(Class<?> testClass) {
Set<Annotation> annotations = new HashSet<>();
Set<Class<?>> seen = new HashSet<>();
collectClassAnnotations(testClass, annotations, seen);
Set<Object> determinedImports = determineImports(annotations, testClass);
this.key = Collections.<Object>unmodifiableSet(
determinedImports != null ? determinedImports : annotations);
}
private void collectClassAnnotations(Class<?> classType,
Set<Annotation> annotations, Set<Class<?>> seen) {
if (seen.add(classType)) {
collectElementAnnotations(classType, annotations, seen);
for (Class<?> interfaceType : classType.getInterfaces()) {
collectClassAnnotations(interfaceType, annotations, seen);
}
if (classType.getSuperclass() != null) {
collectClassAnnotations(classType.getSuperclass(), annotations, seen);
}
}
}
private void collectElementAnnotations(AnnotatedElement element,
Set<Annotation> annotations, Set<Class<?>> seen) {
for (Annotation annotation : element.getDeclaredAnnotations()) {
if (!isIgnoredAnnotation(annotation)) {
annotations.add(annotation);
collectClassAnnotations(annotation.annotationType(), annotations,
seen);
}
}
}
private boolean isIgnoredAnnotation(Annotation annotation) {
for (AnnotationFilter annotationFilter : annotationFilters) {
if (annotationFilter.isIgnored(annotation)) {
return true;
}
}
return false;
}
private Set<Object> determineImports(Set<Annotation> annotations,
Class<?> testClass) {
Set<Object> determinedImports = new LinkedHashSet<>();
AnnotationMetadata testClassMetadata = new StandardAnnotationMetadata(
testClass);
for (Annotation annotation : annotations) {
for (Class<?> source : getImports(annotation)) {
Set<Object> determinedSourceImports = determineImports(source,
testClassMetadata);
if (determinedSourceImports == null) {
return null;
}
determinedImports.addAll(determinedSourceImports);
}
}
return determinedImports;
}
private Class<?>[] getImports(Annotation annotation) {
if (annotation instanceof Import) {
return ((Import) annotation).value();
}
return NO_IMPORTS;
}
private Set<Object> determineImports(Class<?> source,
AnnotationMetadata metadata) {
if (DeterminableImports.class.isAssignableFrom(source)) {
// We can determine the imports
return ((DeterminableImports) instantiate(source))
.determineImports(metadata);
}
if (ImportSelector.class.isAssignableFrom(source)
|| ImportBeanDefinitionRegistrar.class.isAssignableFrom(source)) {
// Standard ImportSelector and ImportBeanDefinitionRegistrar could
// use anything to determine the imports so we can't be sure
return null;
}
// The source itself is the import
return Collections.<Object>singleton(source.getName());
}
@SuppressWarnings("unchecked")
private <T> T instantiate(Class<T> source) {
try {
Constructor<?> constructor = source.getDeclaredConstructor();
ReflectionUtils.makeAccessible(constructor);
return (T) constructor.newInstance();
}
catch (Throwable ex) {
throw new IllegalStateException(
"Unable to instantiate DeterminableImportSelector "
+ source.getName(),
ex);
}
}
@Override
public int hashCode() {
return this.key.hashCode();
}
@Override
public boolean equals(Object obj) {
return (obj != null && getClass().equals(obj.getClass())
&& this.key.equals(((ContextCustomizerKey) obj).key));
}
@Override
public String toString() {
return this.key.toString();
}
}
/**
* Filter used to limit considered annotations.
*/
private interface AnnotationFilter {
boolean isIgnored(Annotation annotation);
}
/**
* {@link AnnotationFilter} for {@literal java.lang} annotations.
*/
private static final class JavaLangAnnotationFilter implements AnnotationFilter {
@Override
public boolean isIgnored(Annotation annotation) {
return AnnotationUtils.isInJavaLangAnnotationPackage(annotation);
}
}
/**
* {@link AnnotationFilter} for Kotlin annotations.
*/
private static final class KotlinAnnotationFilter implements AnnotationFilter {
@Override
public boolean isIgnored(Annotation annotation) {
return "kotlin.Metadata".equals(annotation.annotationType().getName())
|| isInKotlinAnnotationPackage(annotation);
}
private boolean isInKotlinAnnotationPackage(Annotation annotation) {
return annotation.annotationType().getName().startsWith("kotlin.annotation.");
}
}
/**
* {@link AnnotationFilter} for Spock annotations.
*/
private static final class SpockAnnotationFilter implements AnnotationFilter {
@Override
public boolean isIgnored(Annotation annotation) {
return annotation.annotationType().getName().startsWith("org.spockframework.")
|| annotation.annotationType().getName().startsWith("spock.");
}
}
}

View File

@@ -0,0 +1,59 @@
/*
* 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.test.context;
import java.lang.reflect.Method;
import java.util.List;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Import;
import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.test.context.ContextConfigurationAttributes;
import org.springframework.test.context.ContextCustomizer;
import org.springframework.test.context.ContextCustomizerFactory;
import org.springframework.util.Assert;
import org.springframework.util.ReflectionUtils;
/**
* {@link ContextCustomizerFactory} to allow {@code @Import} annotations to be used
* directly on test classes.
*
* @author Phillip Webb
* @see ImportsContextCustomizer
*/
class ImportsContextCustomizerFactory implements ContextCustomizerFactory {
@Override
public ContextCustomizer createContextCustomizer(Class<?> testClass,
List<ContextConfigurationAttributes> configAttributes) {
if (AnnotatedElementUtils.findMergedAnnotation(testClass, Import.class) != null) {
assertHasNoBeanMethods(testClass);
return new ImportsContextCustomizer(testClass);
}
return null;
}
private void assertHasNoBeanMethods(Class<?> testClass) {
ReflectionUtils.doWithMethods(testClass, this::assertHasNoBeanMethods);
}
private void assertHasNoBeanMethods(Method method) {
Assert.state(!AnnotatedElementUtils.isAnnotated(method, Bean.class),
"Test classes cannot include @Bean methods");
}
}

View File

@@ -0,0 +1,35 @@
/*
* 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.test.context;
import org.springframework.test.context.MergedContextConfiguration;
/**
* Encapsulates the <em>merged</em> context configuration declared on a test class and all
* of its superclasses for a reactive web application.
*
* @author Stephane Nicoll
* @since 2.0.0
*/
public class ReactiveWebMergedContextConfiguration extends MergedContextConfiguration {
public ReactiveWebMergedContextConfiguration(
MergedContextConfiguration mergedConfig) {
super(mergedConfig);
}
}

View File

@@ -0,0 +1,104 @@
/*
* Copyright 2012-2016 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.test.context;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.boot.SpringBootConfiguration;
import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider;
import org.springframework.core.type.filter.AnnotationTypeFilter;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
/**
* Internal utility class to scan for a {@link SpringBootConfiguration} class.
*
* @author Phillip Webb
*/
final class SpringBootConfigurationFinder {
private static final Map<String, Class<?>> cache = Collections
.synchronizedMap(new Cache(40));
private final ClassPathScanningCandidateComponentProvider scanner;
SpringBootConfigurationFinder() {
this.scanner = new ClassPathScanningCandidateComponentProvider(false);
this.scanner.addIncludeFilter(
new AnnotationTypeFilter(SpringBootConfiguration.class));
this.scanner.setResourcePattern("*.class");
}
public Class<?> findFromClass(Class<?> source) {
Assert.notNull(source, "Source must not be null");
return findFromPackage(ClassUtils.getPackageName(source));
}
public Class<?> findFromPackage(String source) {
Assert.notNull(source, "Source must not be null");
Class<?> configuration = cache.get(source);
if (configuration == null) {
configuration = scanPackage(source);
cache.put(source, configuration);
}
return configuration;
}
private Class<?> scanPackage(String source) {
while (!source.isEmpty()) {
Set<BeanDefinition> components = this.scanner.findCandidateComponents(source);
if (!components.isEmpty()) {
Assert.state(components.size() == 1,
"Found multiple @SpringBootConfiguration annotated classes "
+ components);
return ClassUtils.resolveClassName(
components.iterator().next().getBeanClassName(), null);
}
source = getParentPackage(source);
}
return null;
}
private String getParentPackage(String sourcePackage) {
int lastDot = sourcePackage.lastIndexOf(".");
return (lastDot == -1 ? "" : sourcePackage.substring(0, lastDot));
}
/**
* Cache implementation based on {@link LinkedHashMap}.
*/
private static class Cache extends LinkedHashMap<String, Class<?>> {
private final int maxSize;
Cache(int maxSize) {
super(16, 0.75f, true);
this.maxSize = maxSize;
}
@Override
protected boolean removeEldestEntry(Map.Entry<String, Class<?>> eldest) {
return size() > this.maxSize;
}
}
}

View File

@@ -0,0 +1,339 @@
/*
* 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.test.context;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import org.springframework.beans.BeanUtils;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.WebApplicationType;
import org.springframework.boot.context.properties.bind.Bindable;
import org.springframework.boot.context.properties.bind.Binder;
import org.springframework.boot.context.properties.source.ConfigurationPropertySource;
import org.springframework.boot.context.properties.source.MapConfigurationPropertySource;
import org.springframework.boot.test.mock.web.SpringBootMockServletContext;
import org.springframework.boot.test.util.TestPropertyValues;
import org.springframework.boot.web.reactive.context.GenericReactiveWebApplicationContext;
import org.springframework.boot.web.servlet.support.ServletContextApplicationContextInitializer;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextInitializer;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.Ordered;
import org.springframework.core.SpringVersion;
import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.core.annotation.Order;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.StandardEnvironment;
import org.springframework.core.io.DefaultResourceLoader;
import org.springframework.test.context.ContextConfigurationAttributes;
import org.springframework.test.context.ContextCustomizer;
import org.springframework.test.context.ContextLoader;
import org.springframework.test.context.MergedContextConfiguration;
import org.springframework.test.context.support.AbstractContextLoader;
import org.springframework.test.context.support.AnnotationConfigContextLoaderUtils;
import org.springframework.test.context.support.TestPropertySourceUtils;
import org.springframework.test.context.web.WebMergedContextConfiguration;
import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.context.support.GenericWebApplicationContext;
/**
* A {@link ContextLoader} that can be used to test Spring Boot applications (those that
* normally startup using {@link SpringApplication}). Although this loader can be used
* directly, most test will instead want to use it with {@link SpringBootTest}.
* <p>
* The loader supports both standard {@link MergedContextConfiguration} as well as
* {@link WebMergedContextConfiguration}. If {@link WebMergedContextConfiguration} is used
* the context will either use a mock servlet environment, or start the full embedded web
* server.
* <p>
* If {@code @ActiveProfiles} are provided in the test class they will be used to create
* the application context.
*
* @author Dave Syer
* @author Phillip Webb
* @author Andy Wilkinson
* @author Stephane Nicoll
* @author Madhura Bhave
* @see SpringBootTest
*/
public class SpringBootContextLoader extends AbstractContextLoader {
private static final Set<String> INTEGRATION_TEST_ANNOTATIONS;
static {
Set<String> annotations = new LinkedHashSet<>();
annotations.add("org.springframework.boot.test.IntegrationTest");
annotations.add("org.springframework.boot.test.WebIntegrationTest");
INTEGRATION_TEST_ANNOTATIONS = Collections.unmodifiableSet(annotations);
}
@Override
public ApplicationContext loadContext(MergedContextConfiguration config)
throws Exception {
Class<?>[] configClasses = config.getClasses();
String[] configLocations = config.getLocations();
Assert.state(
!ObjectUtils.isEmpty(configClasses)
|| !ObjectUtils.isEmpty(configLocations),
"No configuration classes "
+ "or locations found in @SpringApplicationConfiguration. "
+ "For default configuration detection to work you need "
+ "Spring 4.0.3 or better (found " + SpringVersion.getVersion()
+ ").");
SpringApplication application = getSpringApplication();
application.setMainApplicationClass(config.getTestClass());
application.addPrimarySources(Arrays.asList(configClasses));
application.getSources().addAll(Arrays.asList(configLocations));
ConfigurableEnvironment environment = new StandardEnvironment();
if (!ObjectUtils.isEmpty(config.getActiveProfiles())) {
setActiveProfiles(environment, config.getActiveProfiles());
}
TestPropertySourceUtils.addPropertiesFilesToEnvironment(environment,
application.getResourceLoader() == null
? new DefaultResourceLoader(getClass().getClassLoader())
: application.getResourceLoader(),
config.getPropertySourceLocations());
TestPropertySourceUtils.addInlinedPropertiesToEnvironment(environment,
getInlinedProperties(config));
application.setEnvironment(environment);
List<ApplicationContextInitializer<?>> initializers = getInitializers(config,
application);
if (config instanceof WebMergedContextConfiguration) {
application.setWebApplicationType(WebApplicationType.SERVLET);
if (!isEmbeddedWebEnvironment(config)) {
new WebConfigurer().configure(config, application, initializers);
}
}
else if (config instanceof ReactiveWebMergedContextConfiguration) {
application.setWebApplicationType(WebApplicationType.REACTIVE);
if (!isEmbeddedWebEnvironment(config)) {
new ReactiveWebConfigurer().configure(application);
}
}
else {
application.setWebApplicationType(WebApplicationType.NONE);
}
application.setInitializers(initializers);
ConfigurableApplicationContext context = application.run();
return context;
}
/**
* Builds new {@link org.springframework.boot.SpringApplication} instance. You can
* override this method to add custom behavior
* @return {@link org.springframework.boot.SpringApplication} instance
*/
protected SpringApplication getSpringApplication() {
return new SpringApplication();
}
private void setActiveProfiles(ConfigurableEnvironment environment,
String[] profiles) {
TestPropertyValues
.of("spring.profiles.active="
+ StringUtils.arrayToCommaDelimitedString(profiles))
.applyTo(environment);
}
protected String[] getInlinedProperties(MergedContextConfiguration config) {
ArrayList<String> properties = new ArrayList<>();
// JMX bean names will clash if the same bean is used in multiple contexts
disableJmx(properties);
properties.addAll(Arrays.asList(config.getPropertySourceProperties()));
if (!isEmbeddedWebEnvironment(config) && !hasCustomServerPort(properties)) {
properties.add("server.port=-1");
}
return properties.toArray(new String[properties.size()]);
}
private void disableJmx(List<String> properties) {
properties.add("spring.jmx.enabled=false");
}
private boolean hasCustomServerPort(List<String> properties) {
Binder binder = new Binder(convertToConfigurationPropertySource(properties));
return binder.bind("server.port", Bindable.of(String.class)).isBound();
}
private ConfigurationPropertySource convertToConfigurationPropertySource(
List<String> properties) {
String[] array = properties.toArray(new String[properties.size()]);
return new MapConfigurationPropertySource(
TestPropertySourceUtils.convertInlinedPropertiesToMap(array));
}
private List<ApplicationContextInitializer<?>> getInitializers(
MergedContextConfiguration config, SpringApplication application) {
List<ApplicationContextInitializer<?>> initializers = new ArrayList<>();
for (ContextCustomizer contextCustomizer : config.getContextCustomizers()) {
initializers.add(new ContextCustomizerAdapter(contextCustomizer, config));
}
initializers.addAll(application.getInitializers());
for (Class<? extends ApplicationContextInitializer<?>> initializerClass : config
.getContextInitializerClasses()) {
initializers.add(BeanUtils.instantiateClass(initializerClass));
}
if (config.getParent() != null) {
initializers.add(new ParentContextApplicationContextInitializer(
config.getParentApplicationContext()));
}
return initializers;
}
private boolean isEmbeddedWebEnvironment(MergedContextConfiguration config) {
for (String annotation : INTEGRATION_TEST_ANNOTATIONS) {
if (AnnotatedElementUtils.isAnnotated(config.getTestClass(), annotation)) {
return true;
}
}
SpringBootTest annotation = AnnotatedElementUtils
.findMergedAnnotation(config.getTestClass(), SpringBootTest.class);
if (annotation != null && annotation.webEnvironment().isEmbedded()) {
return true;
}
return false;
}
@Override
public void processContextConfiguration(
ContextConfigurationAttributes configAttributes) {
super.processContextConfiguration(configAttributes);
if (!configAttributes.hasResources()) {
Class<?>[] defaultConfigClasses = detectDefaultConfigurationClasses(
configAttributes.getDeclaringClass());
configAttributes.setClasses(defaultConfigClasses);
}
}
/**
* Detect the default configuration classes for the supplied test class. By default
* simply delegates to
* {@link AnnotationConfigContextLoaderUtils#detectDefaultConfigurationClasses} .
* @param declaringClass the test class that declared {@code @ContextConfiguration}
* @return an array of default configuration classes, potentially empty but never
* {@code null}
* @see AnnotationConfigContextLoaderUtils
*/
protected Class<?>[] detectDefaultConfigurationClasses(Class<?> declaringClass) {
return AnnotationConfigContextLoaderUtils
.detectDefaultConfigurationClasses(declaringClass);
}
@Override
public ApplicationContext loadContext(String... locations) throws Exception {
throw new UnsupportedOperationException("SpringApplicationContextLoader "
+ "does not support the loadContext(String...) method");
}
@Override
protected String[] getResourceSuffixes() {
return new String[] { "-context.xml", "Context.groovy" };
}
@Override
protected String getResourceSuffix() {
throw new IllegalStateException();
}
/**
* Inner class to configure {@link WebMergedContextConfiguration}.
*/
private static class WebConfigurer {
private static final Class<GenericWebApplicationContext> WEB_CONTEXT_CLASS = GenericWebApplicationContext.class;
void configure(MergedContextConfiguration configuration,
SpringApplication application,
List<ApplicationContextInitializer<?>> initializers) {
WebMergedContextConfiguration webConfiguration = (WebMergedContextConfiguration) configuration;
addMockServletContext(initializers, webConfiguration);
application.setApplicationContextClass(WEB_CONTEXT_CLASS);
}
private void addMockServletContext(
List<ApplicationContextInitializer<?>> initializers,
WebMergedContextConfiguration webConfiguration) {
SpringBootMockServletContext servletContext = new SpringBootMockServletContext(
webConfiguration.getResourceBasePath());
initializers.add(0, new ServletContextApplicationContextInitializer(
servletContext, true));
}
}
/**
* Inner class to configure {@link ReactiveWebMergedContextConfiguration}.
*/
private static class ReactiveWebConfigurer {
private static final Class<GenericReactiveWebApplicationContext> WEB_CONTEXT_CLASS = GenericReactiveWebApplicationContext.class;
void configure(SpringApplication application) {
application.setApplicationContextClass(WEB_CONTEXT_CLASS);
}
}
/**
* Adapts a {@link ContextCustomizer} to a {@link ApplicationContextInitializer} so
* that it can be triggered via {@link SpringApplication}.
*/
private static class ContextCustomizerAdapter
implements ApplicationContextInitializer<ConfigurableApplicationContext> {
private final ContextCustomizer contextCustomizer;
private final MergedContextConfiguration config;
ContextCustomizerAdapter(ContextCustomizer contextCustomizer,
MergedContextConfiguration config) {
this.contextCustomizer = contextCustomizer;
this.config = config;
}
@Override
public void initialize(ConfigurableApplicationContext applicationContext) {
this.contextCustomizer.customizeContext(applicationContext, this.config);
}
}
@Order(Ordered.HIGHEST_PRECEDENCE)
private static class ParentContextApplicationContextInitializer
implements ApplicationContextInitializer<ConfigurableApplicationContext> {
private final ApplicationContext parent;
ParentContextApplicationContextInitializer(ApplicationContext parent) {
this.parent = parent;
}
@Override
public void initialize(ConfigurableApplicationContext applicationContext) {
applicationContext.setParent(this.parent);
}
}
}

View File

@@ -0,0 +1,161 @@
/*
* 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.test.context;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.SpringBootConfiguration;
import org.springframework.boot.WebApplicationType;
import org.springframework.boot.web.reactive.context.ReactiveWebApplicationContext;
import org.springframework.boot.web.server.LocalServerPort;
import org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.AliasFor;
import org.springframework.core.env.Environment;
import org.springframework.test.context.BootstrapWith;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.ContextLoader;
import org.springframework.web.context.WebApplicationContext;
/**
* Annotation that can be specified on a test class that runs Spring Boot based tests.
* Provides the following features over and above the regular <em>Spring TestContext
* Framework</em>:
* <ul>
* <li>Uses {@link SpringBootContextLoader} as the default {@link ContextLoader} when no
* specific {@link ContextConfiguration#loader() @ContextConfiguration(loader=...)} is
* defined.</li>
* <li>Automatically searches for a
* {@link SpringBootConfiguration @SpringBootConfiguration} when nested
* {@code @Configuration} is not used, and no explicit {@link #classes() classes} are
* specified.</li>
* <li>Allows custom {@link Environment} properties to be defined using the
* {@link #properties() properties attribute}.</li>
* <li>Provides support for different {@link #webEnvironment() webEnvironment} modes,
* including the ability to start a fully running web server listening on a
* {@link WebEnvironment#DEFINED_PORT defined} or {@link WebEnvironment#RANDOM_PORT
* random} port.</li>
* <li>Registers a {@link org.springframework.boot.test.web.client.TestRestTemplate
* TestRestTemplate} bean for use in web tests that are using a fully running web server.
* </li>
* </ul>
*
* @author Phillip Webb
* @author Andy Wilkinson
* @since 1.4.0
* @see ContextConfiguration
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@BootstrapWith(SpringBootTestContextBootstrapper.class)
public @interface SpringBootTest {
/**
* Alias for {@link #properties()}.
* @return the properties to apply
*/
@AliasFor("properties")
String[] value() default {};
/**
* Properties in form {@literal key=value} that should be added to the Spring
* {@link Environment} before the test runs.
* @return the properties to add
*/
@AliasFor("value")
String[] properties() default {};
/**
* The <em>annotated classes</em> to use for loading an
* {@link org.springframework.context.ApplicationContext ApplicationContext}. Can also
* be specified using
* {@link ContextConfiguration#classes() @ContextConfiguration(classes=...)}. If no
* explicit classes are defined the test will look for nested
* {@link Configuration @Configuration} classes, before falling back to a
* {@link SpringBootConfiguration} search.
* @see ContextConfiguration#classes()
* @return the annotated classes used to load the application context
*/
Class<?>[] classes() default {};
/**
* The type of web environment to create when applicable. Defaults to
* {@link WebEnvironment#MOCK}.
* @return the type of web environment
*/
WebEnvironment webEnvironment() default WebEnvironment.MOCK;
/**
* An enumeration web environment modes.
*/
enum WebEnvironment {
/**
* Creates a {@link WebApplicationContext} with a mock servlet environment if
* servlet APIs are on the classpath, a {@link ReactiveWebApplicationContext} if
* Spring WebFlux is on the classpath or a regular {@link ApplicationContext}
* otherwise.
*/
MOCK(false),
/**
* Creates a web application context (reactive or servlet based) and sets a
* {@code server.port=0} {@link Environment} property (which usually triggers
* listening on a random port). Often used in conjunction with a
* {@link LocalServerPort} injected field on the test.
*/
RANDOM_PORT(true),
/**
* Creates a (reactive) web application context without defining any
* {@code server.port=0} {@link Environment} property.
*/
DEFINED_PORT(true),
/**
* Creates an {@link ApplicationContext} and sets
* {@link SpringApplication#setWebApplicationType(WebApplicationType)} to
* {@link WebApplicationType#NONE}.
*/
NONE(false);
private final boolean embedded;
WebEnvironment(boolean embedded) {
this.embedded = embedded;
}
/**
* Return if the environment uses an {@link ServletWebServerApplicationContext}.
* @return if an {@link ServletWebServerApplicationContext} is used.
*/
public boolean isEmbedded() {
return this.embedded;
}
}
}

View File

@@ -0,0 +1,383 @@
/*
* 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.test.context;
import java.lang.annotation.Annotation;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.boot.SpringBootConfiguration;
import org.springframework.boot.WebApplicationType;
import org.springframework.boot.context.properties.bind.Bindable;
import org.springframework.boot.context.properties.bind.Binder;
import org.springframework.boot.context.properties.source.ConfigurationPropertySource;
import org.springframework.boot.context.properties.source.MapConfigurationPropertySource;
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.core.env.Environment;
import org.springframework.core.io.support.SpringFactoriesLoader;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.ContextConfigurationAttributes;
import org.springframework.test.context.ContextHierarchy;
import org.springframework.test.context.ContextLoader;
import org.springframework.test.context.MergedContextConfiguration;
import org.springframework.test.context.TestContext;
import org.springframework.test.context.TestContextBootstrapper;
import org.springframework.test.context.TestExecutionListener;
import org.springframework.test.context.support.DefaultTestContextBootstrapper;
import org.springframework.test.context.support.TestPropertySourceUtils;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.test.context.web.WebMergedContextConfiguration;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.ObjectUtils;
/**
* {@link TestContextBootstrapper} for Spring Boot. Provides support for
* {@link SpringBootTest @SpringBootTest} and may also be used directly or subclassed.
* Provides the following features over and above {@link DefaultTestContextBootstrapper}:
* <ul>
* <li>Uses {@link SpringBootContextLoader} as the
* {@link #getDefaultContextLoaderClass(Class) default context loader}.</li>
* <li>Automatically searches for a
* {@link SpringBootConfiguration @SpringBootConfiguration} when required.</li>
* <li>Allows custom {@link Environment} {@link #getProperties(Class)} to be defined.</li>
* <li>Provides support for different {@link WebEnvironment webEnvironment} modes.</li>
* </ul>
*
* @author Phillip Webb
* @author Andy Wilkinson
* @author Brian Clozel
* @author Madhura Bhave
* @since 1.4.0
* @see SpringBootTest
* @see TestConfiguration
*/
public class SpringBootTestContextBootstrapper extends DefaultTestContextBootstrapper {
private static final String[] WEB_ENVIRONMENT_CLASSES = { "javax.servlet.Servlet",
"org.springframework.web.context.ConfigurableWebApplicationContext" };
private static final String REACTIVE_WEB_ENVIRONMENT_CLASS = "org.springframework."
+ "web.reactive.DispatcherHandler";
private static final String MVC_WEB_ENVIRONMENT_CLASS = "org.springframework."
+ "web.servlet.DispatcherServlet";
private static final String ACTIVATE_SERVLET_LISTENER = "org.springframework.test."
+ "context.web.ServletTestExecutionListener.activateListener";
private static final Log logger = LogFactory
.getLog(SpringBootTestContextBootstrapper.class);
@Override
public TestContext buildTestContext() {
TestContext context = super.buildTestContext();
verifyConfiguration(context.getTestClass());
WebEnvironment webEnvironment = getWebEnvironment(context.getTestClass());
if (webEnvironment == WebEnvironment.MOCK
&& deduceWebApplicationType() == WebApplicationType.SERVLET) {
context.setAttribute(ACTIVATE_SERVLET_LISTENER, true);
}
else if (webEnvironment != null && webEnvironment.isEmbedded()) {
context.setAttribute(ACTIVATE_SERVLET_LISTENER, false);
}
return context;
}
@Override
protected Set<Class<? extends TestExecutionListener>> getDefaultTestExecutionListenerClasses() {
Set<Class<? extends TestExecutionListener>> listeners = super.getDefaultTestExecutionListenerClasses();
List<DefaultTestExecutionListenersPostProcessor> postProcessors = SpringFactoriesLoader
.loadFactories(DefaultTestExecutionListenersPostProcessor.class,
getClass().getClassLoader());
for (DefaultTestExecutionListenersPostProcessor postProcessor : postProcessors) {
listeners = postProcessor.postProcessDefaultTestExecutionListeners(listeners);
}
return listeners;
}
@Override
protected ContextLoader resolveContextLoader(Class<?> testClass,
List<ContextConfigurationAttributes> configAttributesList) {
Class<?>[] classes = getClasses(testClass);
if (!ObjectUtils.isEmpty(classes)) {
for (ContextConfigurationAttributes configAttributes : configAttributesList) {
addConfigAttributesClasses(configAttributes, classes);
}
}
return super.resolveContextLoader(testClass, configAttributesList);
}
private void addConfigAttributesClasses(
ContextConfigurationAttributes configAttributes, Class<?>[] classes) {
List<Class<?>> combined = new ArrayList<>();
combined.addAll(Arrays.asList(classes));
if (configAttributes.getClasses() != null) {
combined.addAll(Arrays.asList(configAttributes.getClasses()));
}
configAttributes.setClasses(combined.toArray(new Class<?>[combined.size()]));
}
@Override
protected Class<? extends ContextLoader> getDefaultContextLoaderClass(
Class<?> testClass) {
return SpringBootContextLoader.class;
}
@Override
protected MergedContextConfiguration processMergedContextConfiguration(
MergedContextConfiguration mergedConfig) {
Class<?>[] classes = getOrFindConfigurationClasses(mergedConfig);
List<String> propertySourceProperties = getAndProcessPropertySourceProperties(
mergedConfig);
mergedConfig = createModifiedConfig(mergedConfig, classes,
propertySourceProperties
.toArray(new String[propertySourceProperties.size()]));
WebEnvironment webEnvironment = getWebEnvironment(mergedConfig.getTestClass());
if (webEnvironment != null && isWebEnvironmentSupported(mergedConfig)) {
WebApplicationType webApplicationType = getWebApplicationType(mergedConfig);
if (webApplicationType == WebApplicationType.SERVLET
&& (webEnvironment.isEmbedded()
|| webEnvironment == WebEnvironment.MOCK)) {
WebAppConfiguration webAppConfiguration = AnnotatedElementUtils
.findMergedAnnotation(mergedConfig.getTestClass(),
WebAppConfiguration.class);
String resourceBasePath = (webAppConfiguration == null ? "src/main/webapp"
: webAppConfiguration.value());
mergedConfig = new WebMergedContextConfiguration(mergedConfig,
resourceBasePath);
}
else if (webApplicationType == WebApplicationType.REACTIVE
&& (webEnvironment.isEmbedded()
|| webEnvironment == WebEnvironment.MOCK)) {
return new ReactiveWebMergedContextConfiguration(mergedConfig);
}
}
return mergedConfig;
}
private WebApplicationType getWebApplicationType(
MergedContextConfiguration configuration) {
ConfigurationPropertySource source = new MapConfigurationPropertySource(
TestPropertySourceUtils.convertInlinedPropertiesToMap(
configuration.getPropertySourceProperties()));
Binder binder = new Binder(source);
return binder
.bind("spring.main.web-application-type",
Bindable.of(WebApplicationType.class))
.orElseGet(this::deduceWebApplicationType);
}
private WebApplicationType deduceWebApplicationType() {
if (ClassUtils.isPresent(REACTIVE_WEB_ENVIRONMENT_CLASS, null)
&& !ClassUtils.isPresent(MVC_WEB_ENVIRONMENT_CLASS, null)) {
return WebApplicationType.REACTIVE;
}
for (String className : WEB_ENVIRONMENT_CLASSES) {
if (!ClassUtils.isPresent(className, null)) {
return WebApplicationType.NONE;
}
}
return WebApplicationType.SERVLET;
}
private boolean isWebEnvironmentSupported(MergedContextConfiguration mergedConfig) {
Class<?> testClass = mergedConfig.getTestClass();
ContextHierarchy hierarchy = AnnotationUtils.getAnnotation(testClass,
ContextHierarchy.class);
if (hierarchy == null || hierarchy.value().length == 0) {
return true;
}
ContextConfiguration[] configurations = hierarchy.value();
return isFromConfiguration(mergedConfig,
configurations[configurations.length - 1]);
}
private boolean isFromConfiguration(MergedContextConfiguration candidateConfig,
ContextConfiguration configuration) {
ContextConfigurationAttributes attributes = new ContextConfigurationAttributes(
candidateConfig.getTestClass(), configuration);
Set<Class<?>> configurationClasses = new HashSet<>(
Arrays.asList(attributes.getClasses()));
for (Class<?> candidate : candidateConfig.getClasses()) {
if (configurationClasses.contains(candidate)) {
return true;
}
}
return false;
}
protected Class<?>[] getOrFindConfigurationClasses(
MergedContextConfiguration mergedConfig) {
Class<?>[] classes = mergedConfig.getClasses();
if (containsNonTestComponent(classes) || mergedConfig.hasLocations()) {
return classes;
}
Class<?> found = new SpringBootConfigurationFinder()
.findFromClass(mergedConfig.getTestClass());
Assert.state(found != null,
"Unable to find a @SpringBootConfiguration, you need to use "
+ "@ContextConfiguration or @SpringBootTest(classes=...) "
+ "with your test");
logger.info("Found @SpringBootConfiguration " + found.getName() + " for test "
+ mergedConfig.getTestClass());
return merge(found, classes);
}
private boolean containsNonTestComponent(Class<?>[] classes) {
for (Class<?> candidate : classes) {
if (!AnnotatedElementUtils.isAnnotated(candidate, TestConfiguration.class)) {
return true;
}
}
return false;
}
private Class<?>[] merge(Class<?> head, Class<?>[] existing) {
Class<?>[] result = new Class<?>[existing.length + 1];
result[0] = head;
System.arraycopy(existing, 0, result, 1, existing.length);
return result;
}
private List<String> getAndProcessPropertySourceProperties(
MergedContextConfiguration mergedConfig) {
List<String> propertySourceProperties = new ArrayList<>(
Arrays.asList(mergedConfig.getPropertySourceProperties()));
String differentiator = getDifferentiatorPropertySourceProperty();
if (differentiator != null) {
propertySourceProperties.add(differentiator);
}
processPropertySourceProperties(mergedConfig, propertySourceProperties);
return propertySourceProperties;
}
/**
* Return a "differentiator" property to ensure that there is something to
* differentiate regular tests and bootstrapped tests. Without this property a cached
* context could be returned that wasn't created by this bootstrapper. By default uses
* the bootstrapper class as a property.
* @return the differentiator or {@code null}
*/
protected String getDifferentiatorPropertySourceProperty() {
return getClass().getName() + "=true";
}
/**
* Post process the property source properties, adding or removing elements as
* required.
* @param mergedConfig the merged context configuration
* @param propertySourceProperties the property source properties to process
*/
protected void processPropertySourceProperties(
MergedContextConfiguration mergedConfig,
List<String> propertySourceProperties) {
Class<?> testClass = mergedConfig.getTestClass();
String[] properties = getProperties(testClass);
if (!ObjectUtils.isEmpty(properties)) {
// Added first so that inlined properties from @TestPropertySource take
// precedence
propertySourceProperties.addAll(0, Arrays.asList(properties));
}
if (getWebEnvironment(testClass) == WebEnvironment.RANDOM_PORT) {
propertySourceProperties.add("server.port=0");
}
}
/**
* Return the {@link WebEnvironment} type for this test or null if undefined.
* @param testClass the source test class
* @return the {@link WebEnvironment} or {@code null}
*/
protected WebEnvironment getWebEnvironment(Class<?> testClass) {
SpringBootTest annotation = getAnnotation(testClass);
return (annotation == null ? null : annotation.webEnvironment());
}
protected Class<?>[] getClasses(Class<?> testClass) {
SpringBootTest annotation = getAnnotation(testClass);
return (annotation == null ? null : annotation.classes());
}
protected String[] getProperties(Class<?> testClass) {
SpringBootTest annotation = getAnnotation(testClass);
return (annotation == null ? null : annotation.properties());
}
protected SpringBootTest getAnnotation(Class<?> testClass) {
return AnnotatedElementUtils.getMergedAnnotation(testClass, SpringBootTest.class);
}
protected void verifyConfiguration(Class<?> testClass) {
SpringBootTest springBootTest = getAnnotation(testClass);
if (springBootTest != null
&& (springBootTest.webEnvironment() == WebEnvironment.DEFINED_PORT
|| springBootTest.webEnvironment() == WebEnvironment.RANDOM_PORT)
&& getAnnotation(WebAppConfiguration.class, testClass) != null) {
throw new IllegalStateException("@WebAppConfiguration should only be used "
+ "with @SpringBootTest when @SpringBootTest is configured with a "
+ "mock web environment. Please remove @WebAppConfiguration or "
+ "reconfigure @SpringBootTest.");
}
}
private <T extends Annotation> T getAnnotation(Class<T> annotationType,
Class<?> testClass) {
return AnnotatedElementUtils.getMergedAnnotation(testClass, annotationType);
}
/**
* Create a new {@link MergedContextConfiguration} with different classes.
* @param mergedConfig the source config
* @param classes the replacement classes
* @return a new {@link MergedContextConfiguration}
*/
protected final MergedContextConfiguration createModifiedConfig(
MergedContextConfiguration mergedConfig, Class<?>[] classes) {
return createModifiedConfig(mergedConfig, classes,
mergedConfig.getPropertySourceProperties());
}
/**
* Create a new {@link MergedContextConfiguration} with different classes and
* properties.
* @param mergedConfig the source config
* @param classes the replacement classes
* @param propertySourceProperties the replacement properties
* @return a new {@link MergedContextConfiguration}
*/
protected final MergedContextConfiguration createModifiedConfig(
MergedContextConfiguration mergedConfig, Class<?>[] classes,
String[] propertySourceProperties) {
return new MergedContextConfiguration(mergedConfig.getTestClass(),
mergedConfig.getLocations(), classes,
mergedConfig.getContextInitializerClasses(),
mergedConfig.getActiveProfiles(),
mergedConfig.getPropertySourceLocations(), propertySourceProperties,
mergedConfig.getContextCustomizers(), mergedConfig.getContextLoader(),
getCacheAwareContextLoaderDelegate(), mergedConfig.getParent());
}
}

View File

@@ -0,0 +1,146 @@
/*
* 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.test.context;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.boot.test.web.client.LocalHostUriTemplateHandler;
import org.springframework.boot.test.web.client.TestRestTemplate;
import org.springframework.boot.test.web.client.TestRestTemplate.HttpClientOption;
import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.boot.web.servlet.server.AbstractServletWebServerFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.test.context.ContextCustomizer;
import org.springframework.test.context.MergedContextConfiguration;
/**
* {@link ContextCustomizer} for {@link SpringBootTest}.
*
* @author Phillip Webb
* @author Andy Wilkinson
*/
class SpringBootTestContextCustomizer implements ContextCustomizer {
@Override
public void customizeContext(ConfigurableApplicationContext context,
MergedContextConfiguration mergedContextConfiguration) {
SpringBootTest annotation = AnnotatedElementUtils.getMergedAnnotation(
mergedContextConfiguration.getTestClass(), SpringBootTest.class);
if (annotation.webEnvironment().isEmbedded()) {
registerTestRestTemplate(context);
}
}
private void registerTestRestTemplate(ConfigurableApplicationContext context) {
ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
if (beanFactory instanceof BeanDefinitionRegistry) {
registerTestRestTemplate(context, (BeanDefinitionRegistry) context);
}
}
private void registerTestRestTemplate(ConfigurableApplicationContext context,
BeanDefinitionRegistry registry) {
registry.registerBeanDefinition(TestRestTemplate.class.getName(),
new RootBeanDefinition(TestRestTemplateFactory.class));
}
@Override
public int hashCode() {
return getClass().hashCode();
}
@Override
public boolean equals(Object obj) {
if (obj == null || obj.getClass() != getClass()) {
return false;
}
return true;
}
/**
* {@link FactoryBean} used to create and configure a {@link TestRestTemplate}.
*/
public static class TestRestTemplateFactory
implements FactoryBean<TestRestTemplate>, ApplicationContextAware {
private static final HttpClientOption[] DEFAULT_OPTIONS = {};
private static final HttpClientOption[] SSL_OPTIONS = { HttpClientOption.SSL };
private TestRestTemplate object;
@Override
public void setApplicationContext(ApplicationContext applicationContext)
throws BeansException {
RestTemplateBuilder builder = getRestTemplateBuilder(applicationContext);
boolean sslEnabled = isSslEnabled(applicationContext);
TestRestTemplate template = new TestRestTemplate(builder.build(), null, null,
sslEnabled ? SSL_OPTIONS : DEFAULT_OPTIONS);
LocalHostUriTemplateHandler handler = new LocalHostUriTemplateHandler(
applicationContext.getEnvironment(), sslEnabled ? "https" : "http");
template.setUriTemplateHandler(handler);
this.object = template;
}
private boolean isSslEnabled(ApplicationContext context) {
try {
AbstractServletWebServerFactory webServerFactory = context
.getBean(AbstractServletWebServerFactory.class);
return webServerFactory.getSsl() != null
&& webServerFactory.getSsl().isEnabled();
}
catch (NoSuchBeanDefinitionException ex) {
return false;
}
}
private RestTemplateBuilder getRestTemplateBuilder(
ApplicationContext applicationContext) {
try {
return applicationContext.getBean(RestTemplateBuilder.class);
}
catch (NoSuchBeanDefinitionException ex) {
return new RestTemplateBuilder();
}
}
@Override
public boolean isSingleton() {
return true;
}
@Override
public Class<?> getObjectType() {
return TestRestTemplate.class;
}
@Override
public TestRestTemplate getObject() throws Exception {
return this.object;
}
}
}

View File

@@ -0,0 +1,44 @@
/*
* Copyright 2012-2016 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.test.context;
import java.util.List;
import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.test.context.ContextConfigurationAttributes;
import org.springframework.test.context.ContextCustomizer;
import org.springframework.test.context.ContextCustomizerFactory;
/**
* {@link ContextCustomizerFactory} for {@link SpringBootTest}.
*
* @author Andy Wilkinson
* @see SpringBootTestContextCustomizer
*/
class SpringBootTestContextCustomizerFactory implements ContextCustomizerFactory {
@Override
public ContextCustomizer createContextCustomizer(Class<?> testClass,
List<ContextConfigurationAttributes> configAttributes) {
if (AnnotatedElementUtils.findMergedAnnotation(testClass,
SpringBootTest.class) != null) {
return new SpringBootTestContextCustomizer();
}
return null;
}
}

View File

@@ -0,0 +1,55 @@
/*
* Copyright 2012-2016 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.test.context;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.boot.context.TypeExcludeFilter;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.stereotype.Component;
/**
* {@link Component @Component} that can be used when a bean is intended only for tests,
* and should be excluded from Spring Boot's component scanning.
* <p>
* Note that if you directly use {@link ComponentScan @ComponentScan} rather than relying
* on {@code @SpringBootApplication} you should ensure that a {@link TypeExcludeFilter} is
* declared as an {@link ComponentScan#excludeFilters() excludeFilter}.
*
* @author Phillip Webb
* @since 1.4.0
* @see TypeExcludeFilter
* @see TestConfiguration
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface TestComponent {
/**
* The value may indicate a suggestion for a logical component name, to be turned into
* a Spring bean in case of an auto-detected component.
* @return the specified bean name, if any
*/
String value() default "";
}

View File

@@ -0,0 +1,52 @@
/*
* Copyright 2012-2016 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.test.context;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.boot.SpringBootConfiguration;
import org.springframework.context.annotation.Configuration;
/**
* {@link Configuration @Configuration} that can be used to define additional beans or
* customizations for a test. Unlike regular {@code @Configuration} classes the use of
* {@code @TestConfiguration} does not prevent auto-detection of
* {@link SpringBootConfiguration @SpringBootConfiguration}.
*
* @author Phillip Webb
* @since 1.4.0
* @see SpringBootTestContextBootstrapper
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
@TestComponent
public @interface TestConfiguration {
/**
* Explicitly specify the name of the Spring bean definition associated with this
* Configuration class. See {@link Configuration#value()} for details.
* @return the specified bean name, if any
*/
String value() default "";
}

View File

@@ -0,0 +1,428 @@
/*
* 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.test.context.assertj;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringReader;
import java.io.StringWriter;
import org.assertj.core.api.AbstractAssert;
import org.assertj.core.api.AbstractObjectArrayAssert;
import org.assertj.core.api.AbstractObjectAssert;
import org.assertj.core.api.AbstractThrowableAssert;
import org.assertj.core.api.Assertions;
import org.assertj.core.api.MapAssert;
import org.assertj.core.error.BasicErrorMessageFactory;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
import org.springframework.context.ApplicationContext;
import org.springframework.util.Assert;
import static org.assertj.core.api.Assertions.assertThat;
/**
* AssertJ {@link org.assertj.core.api.Assert assertions} that can be applied to an
* {@link ApplicationContext}.
*
* @param <C> The application context type
* @author Phillip Webb
* @author Andy Wilkinson
* @since 2.0.0
* @see ApplicationContextRunner
* @see AssertableApplicationContext
*/
public class ApplicationContextAssert<C extends ApplicationContext>
extends AbstractAssert<ApplicationContextAssert<C>, C> {
private final Throwable startupFailure;
/**
* Create a new {@link ApplicationContextAssert} instance.
* @param applicationContext the source application context
* @param startupFailure the startup failure or {@code null}
*/
ApplicationContextAssert(C applicationContext, Throwable startupFailure) {
super(applicationContext, ApplicationContextAssert.class);
Assert.notNull(applicationContext, "ApplicationContext must not be null");
this.startupFailure = startupFailure;
}
/**
* Verifies that the application context contains a bean with the given name.
* <p>
* Example: <pre class="code">
* assertThat(context).hasBean("fooBean"); </pre>
* @param name the name of the bean
* @return {@code this} assertion object.
* @throws AssertionError if the application context did not start
* @throws AssertionError if the application context does not contain a bean with the
* given name
*/
public ApplicationContextAssert<C> hasBean(String name) {
if (this.startupFailure != null) {
throwAssertionError(contextFailedToStartWhenExpecting(
"to have bean named:%n <%s>", name));
}
if (findBean(name) == null) {
throwAssertionError(new BasicErrorMessageFactory(
"%nExpecting:%n <%s>%nto have bean named:%n <%s>%nbut found no such bean",
getApplicationContext(), name));
}
return this;
}
/**
* Verifies that the application context contains a single bean with the given type.
* <p>
* Example: <pre class="code">
* assertThat(context).hasSingleBean(Foo.class); </pre>
* @param type the bean type
* @return {@code this} assertion object.
* @throws AssertionError if the application context did not start
* @throws AssertionError if the application context does no beans of the given type
* @throws AssertionError if the application context contains multiple beans of the
* given type
*/
public ApplicationContextAssert<C> hasSingleBean(Class<?> type) {
if (this.startupFailure != null) {
throwAssertionError(contextFailedToStartWhenExpecting(
"to have a single bean of type:%n <%s>", type));
}
String[] names = getApplicationContext().getBeanNamesForType(type);
if (names.length == 0) {
throwAssertionError(new BasicErrorMessageFactory(
"%nExpecting:%n <%s>%nto have a single bean of type:%n <%s>%nbut found no beans of that type",
getApplicationContext(), type));
}
if (names.length > 1) {
throwAssertionError(new BasicErrorMessageFactory(
"%nExpecting:%n <%s>%nto have a single bean of type:%n <%s>%nbut found:%n <%s>",
getApplicationContext(), type, names));
}
return this;
}
/**
* Verifies that the application context does not contain any beans of the given type.
* <p>
* Example: <pre class="code">
* assertThat(context).doesNotHaveBean(Foo.class); </pre>
* @param type the bean type
* @return {@code this} assertion object.
* @throws AssertionError if the application context did not start
* @throws AssertionError if the application context contains any beans of the given
* type
*/
public ApplicationContextAssert<C> doesNotHaveBean(Class<?> type) {
if (this.startupFailure != null) {
throwAssertionError(contextFailedToStartWhenExpecting(
"not to have any beans of type:%n <%s>", type));
}
String[] names = getApplicationContext().getBeanNamesForType(type);
if (names.length > 0) {
throwAssertionError(new BasicErrorMessageFactory(
"%nExpecting:%n <%s>%nnot to have a beans of type:%n <%s>%nbut found:%n <%s>",
getApplicationContext(), type, names));
}
return this;
}
/**
* Verifies that the application context does not contain a beans of the given name.
* <p>
* Example: <pre class="code">
* assertThat(context).doesNotHaveBean("fooBean"); </pre>
* @param name the name of the bean
* @return {@code this} assertion object.
* @throws AssertionError if the application context did not start
* @throws AssertionError if the application context contains a beans of the given
* name
*/
public ApplicationContextAssert<C> doesNotHaveBean(String name) {
if (this.startupFailure != null) {
throwAssertionError(contextFailedToStartWhenExpecting(
"not to have any beans of name:%n <%s>", name));
}
try {
Object bean = getApplicationContext().getBean(name);
throwAssertionError(new BasicErrorMessageFactory(
"%nExpecting:%n <%s>%nnot to have a bean of name:%n <%s>%nbut found:%n <%s>",
getApplicationContext(), name, bean));
}
catch (NoSuchBeanDefinitionException ex) {
}
return this;
}
/**
* Obtain the beans names of the given type from the application context, the names
* becoming the object array under test.
* <p>
* Example: <pre class="code">
* assertThat(context).getBeanNames(Foo.class).containsOnly("fooBean"); </pre>
* @param <T> the bean type
* @param type the bean type
* @return array assertions for the bean names
* @throws AssertionError if the application context did not start
*/
public <T> AbstractObjectArrayAssert<?, String> getBeanNames(Class<T> type) {
if (this.startupFailure != null) {
throwAssertionError(contextFailedToStartWhenExpecting(
"to get beans names with type:%n <%s>", type));
}
return Assertions.assertThat(getApplicationContext().getBeanNamesForType(type))
.as("Bean names of type <%s> from <%s>", type, getApplicationContext());
}
/**
* Obtain a single bean of the given type from the application context, the bean
* becoming the object under test. If no beans of the specified type can be found an
* assert on {@code null} is returned.
* <p>
* Example: <pre class="code">
* assertThat(context).getBean(Foo.class).isInstanceOf(DefaultFoo.class);
* assertThat(context).getBean(Bar.class).isNull();</pre>
* @param <T> the bean type
* @param type the bean type
* @return bean assertions for the bean, or an assert on {@code null} if the no bean
* is found
* @throws AssertionError if the application context did not start
* @throws AssertionError if the application context contains multiple beans of the
* given type
*/
public <T> AbstractObjectAssert<?, T> getBean(Class<T> type) {
if (this.startupFailure != null) {
throwAssertionError(contextFailedToStartWhenExpecting(
"to contain bean of type:%n <%s>", type));
}
String[] names = getApplicationContext().getBeanNamesForType(type);
if (names.length > 1) {
throwAssertionError(new BasicErrorMessageFactory(
"%nExpecting:%n <%s>%nsingle bean of type:%n <%s>%nbut found:%n <%s>",
getApplicationContext(), type, names));
}
T bean = (names.length == 0 ? null
: getApplicationContext().getBean(names[0], type));
return Assertions.assertThat(bean).as("Bean of type <%s> from <%s>", type,
getApplicationContext());
}
/**
* Obtain a single bean of the given name from the application context, the bean
* becoming the object under test. If no bean of the specified name can be found an
* assert on {@code null} is returned.
* <p>
* Example: <pre class="code">
* assertThat(context).getBean("foo").isInstanceOf(Foo.class);
* assertThat(context).getBean("foo").isNull();</pre>
* @param name the name of the bean
* @return bean assertions for the bean, or an assert on {@code null} if the no bean
* is found
* @throws AssertionError if the application context did not start
*/
public AbstractObjectAssert<?, Object> getBean(String name) {
if (this.startupFailure != null) {
throwAssertionError(contextFailedToStartWhenExpecting(
"to contain a bean of name:%n <%s>", name));
}
Object bean = findBean(name);
return Assertions.assertThat(bean).as("Bean of name <%s> from <%s>", name,
getApplicationContext());
}
/**
* Obtain a single bean of the given name and type from the application context, the
* bean becoming the object under test. If no bean of the specified name can be found
* an assert on {@code null} is returned.
* <p>
* Example: <pre class="code">
* assertThat(context).getBean("foo", Foo.class).isInstanceOf(DefaultFoo.class);
* assertThat(context).getBean("foo", Foo.class).isNull();</pre>
* @param <T> the bean type
* @param name the name of the bean
* @param type the bean type
* @return bean assertions for the bean, or an assert on {@code null} if the no bean
* is found
* @throws AssertionError if the application context did not start
* @throws AssertionError if the application context contains a bean with the given
* name but a different type
*/
@SuppressWarnings("unchecked")
public <T> AbstractObjectAssert<?, T> getBean(String name, Class<T> type) {
if (this.startupFailure != null) {
throwAssertionError(contextFailedToStartWhenExpecting(
"to contain a bean of name:%n <%s> (%s)", name, type));
}
Object bean = findBean(name);
if (bean != null && type != null && !type.isInstance(bean)) {
throwAssertionError(new BasicErrorMessageFactory(
"%nExpecting:%n <%s>%nto contain a bean of name:%n <%s> (%s)%nbut found:%n <%s> of type <%s>",
getApplicationContext(), name, type, bean, bean.getClass()));
}
return Assertions.assertThat((T) bean).as(
"Bean of name <%s> and type <%s> from <%s>", name, type,
getApplicationContext());
}
private Object findBean(String name) {
try {
return getApplicationContext().getBean(name);
}
catch (NoSuchBeanDefinitionException ex) {
return null;
}
}
/**
* Obtain a map bean names and instances of the given type from the application
* context, the map becoming the object under test. If no bean of the specified type
* can be found an assert on an empty {@code map} is returned.
* <p>
* Example: <pre class="code">
* assertThat(context).getBeans(Foo.class).containsKey("foo");
* </pre>
* @param <T> the bean type
* @param type the bean type
* @return bean assertions for the beans, or an assert on an empty {@code map} if the
* no beans are found
* @throws AssertionError if the application context did not start
*/
public <T> MapAssert<String, T> getBeans(Class<T> type) {
if (this.startupFailure != null) {
throwAssertionError(contextFailedToStartWhenExpecting(
"to get beans of type:%n <%s>", type));
}
return Assertions.assertThat(getApplicationContext().getBeansOfType(type))
.as("Beans of type <%s> from <%s>", type, getApplicationContext());
}
/**
* Obtain the failure that stopped the application context from running, the failure
* becoming the object under test.
* <p>
* Example: <pre class="code">
* assertThat(context).getFailure().containsMessage("missing bean");
* </pre>
* @return assertions on the cause of the failure
* @throws AssertionError if the application context started without a failure
*/
public AbstractThrowableAssert<?, ? extends Throwable> getFailure() {
hasFailed();
return assertThat(this.startupFailure);
}
/**
* Verifies that the application has failed to start.
* <p>
* Example: <pre class="code"> assertThat(context).hasFailed();
* </pre>
* @return {@code this} assertion object.
* @throws AssertionError if the application context started without a failure
*/
public ApplicationContextAssert<C> hasFailed() {
if (this.startupFailure == null) {
throwAssertionError(new BasicErrorMessageFactory(
"%nExpecting:%n <%s>%nto have failed%nbut context started successfully",
getApplicationContext()));
}
return this;
}
/**
* Verifies that the application has not failed to start.
* <p>
* Example: <pre class="code"> assertThat(context).hasNotFailed();
* </pre>
* @return {@code this} assertion object.
* @throws AssertionError if the application context failed to start
*/
public ApplicationContextAssert<C> hasNotFailed() {
if (this.startupFailure != null) {
throwAssertionError(contextFailedToStartWhenExpecting("to have not failed"));
}
return this;
}
protected final C getApplicationContext() {
return this.actual;
}
protected final Throwable getStartupFailure() {
return this.startupFailure;
}
private ContextFailedToStart<C> contextFailedToStartWhenExpecting(
String expectationFormat, Object... arguments) {
return new ContextFailedToStart<>(getApplicationContext(), this.startupFailure,
expectationFormat, arguments);
}
private static final class ContextFailedToStart<C extends ApplicationContext>
extends BasicErrorMessageFactory {
private ContextFailedToStart(C context, Throwable ex, String expectationFormat,
Object... arguments) {
super("%nExpecting:%n <%s>%n" + expectationFormat
+ ":%nbut context failed to start:%n%s",
combineArguments(context.toString(), ex, arguments));
}
private static Object[] combineArguments(String context, Throwable ex,
Object[] arguments) {
Object[] combinedArguments = new Object[arguments.length + 2];
combinedArguments[0] = unquotedString(context);
System.arraycopy(arguments, 0, combinedArguments, 1, arguments.length);
combinedArguments[combinedArguments.length - 1] = unquotedString(
getIndentedStackTraceAsString(ex));
return combinedArguments;
}
private static String getIndentedStackTraceAsString(Throwable ex) {
String stackTrace = getStackTraceAsString(ex);
return indent(stackTrace);
}
private static String getStackTraceAsString(Throwable ex) {
StringWriter writer = new StringWriter();
PrintWriter printer = new PrintWriter(writer);
ex.printStackTrace(printer);
return writer.toString();
}
private static String indent(String input) {
BufferedReader reader = new BufferedReader(new StringReader(input));
StringWriter writer = new StringWriter();
PrintWriter printer = new PrintWriter(writer);
try {
String line;
while ((line = reader.readLine()) != null) {
printer.print(" ");
printer.println(line);
}
return writer.toString();
}
catch (IOException ex) {
return input;
}
}
}
}

View File

@@ -0,0 +1,116 @@
/*
* 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.test.context.assertj;
import java.io.Closeable;
import java.lang.reflect.Proxy;
import java.util.function.Supplier;
import org.assertj.core.api.AssertProvider;
import org.springframework.context.ApplicationContext;
import org.springframework.util.Assert;
/**
* An {@link ApplicationContext} that additionally supports AssertJ style assertions. Can
* be used to decorate and existing application context or an application context that
* failed to start.
* <p>
* Assertions can be applied using the standard AssertJ {@code assertThat(...)} style (see
* {@link ApplicationContextAssert} for a complete list). For example: <pre class="code">
* assertThat(applicationContext).hasSingleBean(MyBean.class);
* </pre>
* <p>
* If the original {@link ApplicationContext} is needed for any reason the
* {@link #getSourceApplicationContext()} method can be used.
* <p>
* Any {@link ApplicationContext} method called on a context that has failed to start will
* throw an {@link IllegalStateException}.
*
* @param <C> The application context type
* @author Phillip Webb
* @see AssertableApplicationContext
* @see AssertableWebApplicationContext
* @see AssertableReactiveWebApplicationContext
* @see ApplicationContextAssert
*/
public interface ApplicationContextAssertProvider<C extends ApplicationContext> extends
ApplicationContext, AssertProvider<ApplicationContextAssert<C>>, Closeable {
/**
* Return an assert for AspectJ.
* @return an AspectJ assert
* @deprecated use standard AssertJ {@code assertThat(context)...} calls instead.
*/
@Deprecated
@Override
ApplicationContextAssert<C> assertThat();
/**
* Return the original source {@link ApplicationContext}.
* @return the source application context
* @throws IllegalStateException if the source context failed to start
*/
C getSourceApplicationContext();
/**
* Return the original source {@link ApplicationContext}, casting it to the requested
* type.
* @param <T> the context type
* @param requiredType the required context type
* @return the source application context
* @throws IllegalStateException if the source context failed to start
*/
<T extends C> T getSourceApplicationContext(Class<T> requiredType);
/**
* Return the failure that caused application context to fail or {@code null} if the
* context started without issue.
* @return the startup failure or {@code null}
*/
Throwable getStartupFailure();
@Override
void close();
/**
* Factory method to create a new {@link ApplicationContextAssertProvider} instance.
* @param <T> the assert provider type
* @param <C> the context type
* @param type the type of {@link ApplicationContextAssertProvider} required (must be
* an interface)
* @param contextType the type of {@link ApplicationContext} being managed (must be an
* interface)
* @param contextSupplier a supplier that will either return a fully configured
* {@link ApplicationContext} or throw an exception if the context fails to start.
* @return a {@link ApplicationContextAssertProvider} instance
*/
@SuppressWarnings("unchecked")
static <T extends ApplicationContextAssertProvider<C>, C extends ApplicationContext> T get(
Class<T> type, Class<? extends C> contextType,
Supplier<? extends C> contextSupplier) {
Assert.notNull(type, "Type must not be null");
Assert.isTrue(type.isInterface(), "Type must be an interface");
Assert.notNull(contextType, "ContextType must not be null");
Assert.isTrue(contextType.isInterface(), "ContextType must be an interface");
Class<?>[] interfaces = { type, contextType };
return (T) Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(),
interfaces, new AssertProviderApplicationContextInvocationHandler(
contextType, contextSupplier));
}
}

View File

@@ -0,0 +1,170 @@
/*
* 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.test.context.assertj;
import java.io.Closeable;
import java.io.IOException;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.function.Supplier;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.springframework.context.ApplicationContext;
import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;
/**
* {@link InvocationHandler} used by {@link ApplicationContextAssertProvider} generated
* proxies.
*
* @author Phillip Webb
*/
class AssertProviderApplicationContextInvocationHandler implements InvocationHandler {
private final Class<?> applicationContextType;
private final ApplicationContext applicationContext;
private final RuntimeException startupFailure;
AssertProviderApplicationContextInvocationHandler(Class<?> applicationContextType,
Supplier<?> contextSupplier) {
this.applicationContextType = applicationContextType;
Object contextOrStartupFailure = getContextOrStartupFailure(contextSupplier);
if (contextOrStartupFailure instanceof RuntimeException) {
this.applicationContext = null;
this.startupFailure = (RuntimeException) contextOrStartupFailure;
}
else {
this.applicationContext = (ApplicationContext) contextOrStartupFailure;
this.startupFailure = null;
}
}
private Object getContextOrStartupFailure(Supplier<?> contextSupplier) {
try {
return contextSupplier.get();
}
catch (RuntimeException ex) {
return ex;
}
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (isToString(method)) {
return toString();
}
if (isGetSourceContext(method)) {
return getSourceContext(args);
}
if (isGetStartupFailure(method)) {
return getStartupFailure();
}
if (isAssertThat(method)) {
return getAssertThat(proxy);
}
if (isCloseMethod(method)) {
return invokeClose();
}
return invokeApplicationContextMethod(method, args);
}
private boolean isToString(Method method) {
return ("toString".equals(method.getName()) && method.getParameterCount() == 0);
}
@Override
public String toString() {
if (this.startupFailure != null) {
return "Unstarted application context "
+ this.applicationContextType.getName() + "[startupFailure="
+ this.startupFailure.getClass().getName() + "]";
}
ToStringBuilder builder = new ToStringBuilder(this.applicationContext)
.append("id", this.applicationContext.getId())
.append("applicationName", this.applicationContext.getApplicationName())
.append("beanDefinitionCount",
this.applicationContext.getBeanDefinitionCount());
return "Started application " + builder;
}
private boolean isGetSourceContext(Method method) {
return "getSourceApplicationContext".equals(method.getName())
&& ((method.getParameterCount() == 0) || Arrays.equals(
new Class<?>[] { Class.class }, method.getParameterTypes()));
}
private Object getSourceContext(Object[] args) {
ApplicationContext context = getStartedApplicationContext();
if (!ObjectUtils.isEmpty(args)) {
Assert.isInstanceOf((Class<?>) args[0], context);
}
return context;
}
private boolean isGetStartupFailure(Method method) {
return ("getStartupFailure".equals(method.getName())
&& method.getParameterCount() == 0);
}
private Object getStartupFailure() {
return this.startupFailure;
}
private boolean isAssertThat(Method method) {
return ("assertThat".equals(method.getName()) && method.getParameterCount() == 0);
}
private Object getAssertThat(Object proxy) {
return new ApplicationContextAssert<>((ApplicationContext) proxy,
this.startupFailure);
}
private boolean isCloseMethod(Method method) {
return ("close".equals(method.getName()) && method.getParameterCount() == 0);
}
private Object invokeClose() throws IOException {
if (this.applicationContext instanceof Closeable) {
((Closeable) this.applicationContext).close();
}
return null;
}
private Object invokeApplicationContextMethod(Method method, Object[] args)
throws Throwable {
try {
return method.invoke(getStartedApplicationContext(), args);
}
catch (InvocationTargetException ex) {
throw ex.getTargetException();
}
}
private ApplicationContext getStartedApplicationContext() {
if (this.startupFailure != null) {
throw new IllegalStateException(toString() + " failed to start",
this.startupFailure);
}
return this.applicationContext;
}
}

View File

@@ -0,0 +1,53 @@
/*
* 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.test.context.assertj;
import java.util.function.Supplier;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ConfigurableApplicationContext;
/**
* An {@link ApplicationContext} that additionally supports AssertJ style assertions. Can
* be used to decorate and existing application context or an application context that
* failed to start.
* <p>
* See {@link ApplicationContextAssertProvider} for more details.
*
* @author Phillip Webb
* @since 2.0.0
* @see ApplicationContextRunner
* @see ApplicationContext
*/
public interface AssertableApplicationContext
extends ApplicationContextAssertProvider<ConfigurableApplicationContext> {
/**
* Factory method to create a new {@link AssertableApplicationContext} instance.
* @param contextSupplier a supplier that will either return a fully configured
* {@link ConfigurableApplicationContext} or throw an exception if the context fails
* to start.
* @return an {@link AssertableApplicationContext} instance
*/
static AssertableApplicationContext get(
Supplier<? extends ConfigurableApplicationContext> contextSupplier) {
return ApplicationContextAssertProvider.get(AssertableApplicationContext.class,
ConfigurableApplicationContext.class, contextSupplier);
}
}

View File

@@ -0,0 +1,55 @@
/*
* 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.test.context.assertj;
import java.util.function.Supplier;
import org.springframework.boot.web.reactive.context.ConfigurableReactiveWebApplicationContext;
import org.springframework.boot.web.reactive.context.ReactiveWebApplicationContext;
/**
* A {@link ReactiveWebApplicationContext} that additionally supports AssertJ style
* assertions. Can be used to decorate and existing reactive web application context or an
* application context that failed to start.
* <p>
* See {@link ApplicationContextAssertProvider} for more details.
*
* @author Phillip Webb
* @since 2.0.0
* @see ReactiveWebApplicationContext
* @see ReactiveWebApplicationContext
*/
public interface AssertableReactiveWebApplicationContext extends
ApplicationContextAssertProvider<ConfigurableReactiveWebApplicationContext>,
ReactiveWebApplicationContext {
/**
* Factory method to create a new {@link AssertableReactiveWebApplicationContext}
* instance.
* @param contextSupplier a supplier that will either return a fully configured
* {@link ConfigurableReactiveWebApplicationContext} or throw an exception if the
* context fails to start.
* @return a {@link AssertableReactiveWebApplicationContext} instance
*/
static AssertableReactiveWebApplicationContext get(
Supplier<? extends ConfigurableReactiveWebApplicationContext> contextSupplier) {
return ApplicationContextAssertProvider.get(
AssertableReactiveWebApplicationContext.class,
ConfigurableReactiveWebApplicationContext.class, contextSupplier);
}
}

View File

@@ -0,0 +1,54 @@
/*
* 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.test.context.assertj;
import java.util.function.Supplier;
import org.springframework.boot.test.context.runner.WebApplicationContextRunner;
import org.springframework.web.context.ConfigurableWebApplicationContext;
import org.springframework.web.context.WebApplicationContext;
/**
* A {@link WebApplicationContext} that additionally supports AssertJ style assertions.
* Can be used to decorate and existing servlet web application context or an application
* context that failed to start.
* <p>
* See {@link ApplicationContextAssertProvider} for more details.
*
* @author Phillip Webb
* @since 2.0.0
* @see WebApplicationContextRunner
* @see WebApplicationContext
*/
public interface AssertableWebApplicationContext
extends ApplicationContextAssertProvider<ConfigurableWebApplicationContext>,
WebApplicationContext {
/**
* Factory method to create a new {@link AssertableWebApplicationContext} instance.
* @param contextSupplier a supplier that will either return a fully configured
* {@link ConfigurableWebApplicationContext} or throw an exception if the context
* fails to start.
* @return a {@link AssertableWebApplicationContext} instance
*/
static AssertableWebApplicationContext get(
Supplier<? extends ConfigurableWebApplicationContext> contextSupplier) {
return ApplicationContextAssertProvider.get(AssertableWebApplicationContext.class,
ConfigurableWebApplicationContext.class, contextSupplier);
}
}

View File

@@ -0,0 +1,20 @@
/*
* 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.
*/
/**
* AssertJ support for ApplicationContexts.
*/
package org.springframework.boot.test.context.assertj;

View File

@@ -0,0 +1,55 @@
/*
* Copyright 2012-2016 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.test.context.filter;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.test.context.ContextCustomizer;
import org.springframework.test.context.MergedContextConfiguration;
/**
* {@link ContextCustomizer} to add the {@link TestTypeExcludeFilter} to the
* {@link ApplicationContext}.
*
* @author Phillip Webb
*/
class ExcludeFilterContextCustomizer implements ContextCustomizer {
@Override
public void customizeContext(ConfigurableApplicationContext context,
MergedContextConfiguration mergedContextConfiguration) {
context.getBeanFactory().registerSingleton(TestTypeExcludeFilter.class.getName(),
new TestTypeExcludeFilter());
}
@Override
public int hashCode() {
return getClass().hashCode();
}
@Override
public boolean equals(Object obj) {
if (obj == this) {
return true;
}
if (obj == null || getClass() != obj.getClass()) {
return false;
}
return true;
}
}

View File

@@ -0,0 +1,41 @@
/*
* Copyright 2012-2016 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.test.context.filter;
import java.util.List;
import org.springframework.context.ApplicationContext;
import org.springframework.test.context.ContextConfigurationAttributes;
import org.springframework.test.context.ContextCustomizer;
import org.springframework.test.context.ContextCustomizerFactory;
/**
* {@link ContextCustomizerFactory} to add the {@link TestTypeExcludeFilter} to the
* {@link ApplicationContext}.
*
* @author Phillip Webb
* @see ExcludeFilterContextCustomizer
*/
class ExcludeFilterContextCustomizerFactory implements ContextCustomizerFactory {
@Override
public ContextCustomizer createContextCustomizer(Class<?> testClass,
List<ContextConfigurationAttributes> configAttributes) {
return new ExcludeFilterContextCustomizer();
}
}

View File

@@ -0,0 +1,85 @@
/*
* 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.test.context.filter;
import java.io.IOException;
import org.springframework.boot.context.TypeExcludeFilter;
import org.springframework.boot.test.context.TestComponent;
import org.springframework.core.type.classreading.MetadataReader;
import org.springframework.core.type.classreading.MetadataReaderFactory;
/**
* {@link TypeExcludeFilter} to exclude classes annotated with {@link TestComponent} as
* well as inner-classes of tests.
*
* @author Phillip Webb
* @author Andy Wilkinson
*/
class TestTypeExcludeFilter extends TypeExcludeFilter {
private static final String[] CLASS_ANNOTATIONS = { "org.junit.runner.RunWith",
"org.junit.jupiter.api.extension.ExtendWith" };
private static final String[] METHOD_ANNOTATIONS = { "org.junit.Test",
"org.junit.platform.commons.annotation.Testable", };
@Override
public boolean match(MetadataReader metadataReader,
MetadataReaderFactory metadataReaderFactory) throws IOException {
if (isTestConfiguration(metadataReader)) {
return true;
}
if (isTestClass(metadataReader)) {
return true;
}
String enclosing = metadataReader.getClassMetadata().getEnclosingClassName();
if (enclosing != null) {
try {
if (match(metadataReaderFactory.getMetadataReader(enclosing),
metadataReaderFactory)) {
return true;
}
}
catch (Exception ex) {
// Ignore
}
}
return false;
}
private boolean isTestConfiguration(MetadataReader metadataReader) {
return (metadataReader.getAnnotationMetadata()
.isAnnotated(TestComponent.class.getName()));
}
private boolean isTestClass(MetadataReader metadataReader) {
for (String annotation : CLASS_ANNOTATIONS) {
if (metadataReader.getAnnotationMetadata().hasAnnotation(annotation)) {
return true;
}
}
for (String annotation : METHOD_ANNOTATIONS) {
if (metadataReader.getAnnotationMetadata().hasAnnotatedMethods(annotation)) {
return true;
}
}
return false;
}
}

View File

@@ -0,0 +1,20 @@
/*
* Copyright 2012-2016 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.
*/
/**
* Test support for {@link org.springframework.boot.context.TypeExcludeFilter}.
*/
package org.springframework.boot.test.context.filter;

View File

@@ -0,0 +1,21 @@
/*
* Copyright 2012-2016 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.
*/
/**
* Classes and annotations related to configuring Spring's {@code ApplicationContext} for
* tests.
*/
package org.springframework.boot.test.context;

View File

@@ -0,0 +1,304 @@
/*
* 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.test.context.runner;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.function.Supplier;
import org.springframework.boot.context.annotation.Configurations;
import org.springframework.boot.context.annotation.UserConfigurations;
import org.springframework.boot.test.context.HidePackagesClassLoader;
import org.springframework.boot.test.context.assertj.ApplicationContextAssert;
import org.springframework.boot.test.context.assertj.ApplicationContextAssertProvider;
import org.springframework.boot.test.util.TestPropertyValues;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.AnnotationConfigRegistry;
import org.springframework.core.ResolvableType;
import org.springframework.core.env.Environment;
import org.springframework.core.io.DefaultResourceLoader;
import org.springframework.util.Assert;
/**
* Utility design to run and an {@link ApplicationContext} and provide AssertJ style
* assertions. The test is best used as a field of a test class, describing the shared
* configuration required for the test:
*
* <pre class="code">
* public class MyContextTests {
* private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
* .withPropertyValues("spring.foo=bar")
* .withUserConfiguration(MyConfiguration.class);
* }</pre>
*
* <p>
* The initialization above makes sure to register {@code MyConfiguration} for all tests
* and set the {@code spring.foo} property to {@code bar} unless specified otherwise.
* <p>
* Based on the configuration above, a specific test can simulate what will happen when
* the context runs, perhaps with overridden property values:
*
* <pre class="code">
* &#064;Test
* public someTest() {
* this.contextRunner.withPropertyValues("spring.foo=biz").run((context) -&gt; {
* assertThat(context).containsSingleBean(MyBean.class);
* // other assertions
* });
* }</pre>
* <p>
* The test above has changed the {@code spring.foo} property to {@code biz} and is
* asserting that the context contains a single {@code MyBean} bean. The
* {@link #run(ContextConsumer) run} method takes a {@link ContextConsumer} that can apply
* assertions to the context. Upon completion, the context is automatically closed.
* <p>
* If the application context fails to start the {@code #run(ContextConsumer)} method is
* called with a "failed" application context. Calls to the context will throw an
* {@link IllegalStateException} and assertions that expect a running context will fail.
* The {@link ApplicationContextAssert#getFailure() getFailure()} assertion can be used if
* further checks are required on the cause of the failure: <pre class="code">
* &#064;Test
* public someTest() {
* this.context.withPropertyValues("spring.foo=fails").run((loaded) -&gt; {
* assertThat(loaded).getFailure().hasCauseInstanceOf(BadPropertyException.class);
* // other assertions
* });
* }</pre>
* <p>
*
* @param <SELF> The "self" type for this runner
* @param <C> The context type
* @param <A> The application context assertion provider
* @author Stephane Nicoll
* @author Andy Wilkinson
* @author Phillip Webb
* @since 2.0.0
* @see ApplicationContextRunner
* @see WebApplicationContextRunner
* @see ReactiveWebApplicationContextRunner
* @see ApplicationContextAssert
*/
abstract class AbstractApplicationContextRunner<SELF extends AbstractApplicationContextRunner<SELF, C, A>, C extends ConfigurableApplicationContext, A extends ApplicationContextAssertProvider<C>> {
private final Supplier<C> contextFactory;
private final TestPropertyValues environmentProperties;
private final TestPropertyValues systemProperties;
private final ClassLoader classLoader;
private final ApplicationContext parent;
private final List<Configurations> configurations;
/**
* Create a new {@link AbstractApplicationContextRunner} instance.
* @param contextFactory the factory used to create the actual context
*/
protected AbstractApplicationContextRunner(Supplier<C> contextFactory) {
this(contextFactory, TestPropertyValues.empty(), TestPropertyValues.empty(), null,
null, Collections.emptyList());
}
/**
* Create a new {@link AbstractApplicationContextRunner} instance.
* @param contextFactory the factory used to create the actual context
* @param environmentProperties the environment properties
* @param systemProperties the system properties
* @param classLoader the class loader
* @param parent the parent
* @param configurations the configuration
*/
protected AbstractApplicationContextRunner(Supplier<C> contextFactory,
TestPropertyValues environmentProperties, TestPropertyValues systemProperties,
ClassLoader classLoader, ApplicationContext parent,
List<Configurations> configurations) {
Assert.notNull(contextFactory, "ContextFactory must not be null");
Assert.notNull(environmentProperties, "EnvironmentProperties must not be null");
Assert.notNull(systemProperties, "SystemProperties must not be null");
Assert.notNull(configurations, "Configurations must not be null");
this.contextFactory = contextFactory;
this.environmentProperties = environmentProperties;
this.systemProperties = systemProperties;
this.classLoader = classLoader;
this.parent = parent;
this.configurations = Collections.unmodifiableList(configurations);
}
/**
* Add the specified {@link Environment} property pairs. Key-value pairs can be
* specified with colon (":") or equals ("=") separators. Override matching keys that
* might have been specified previously.
* @param pairs the key-value pairs for properties that need to be added to the
* environment
* @return a new instance with the updated property values
* @see TestPropertyValues
* @see #withSystemProperties(String...)
*/
public SELF withPropertyValues(String... pairs) {
return newInstance(this.contextFactory, this.environmentProperties.and(pairs),
this.systemProperties, this.classLoader, this.parent,
this.configurations);
}
/**
* Add the specified {@link System} property pairs. Key-value pairs can be specified
* with colon (":") or equals ("=") separators. System properties are added before the
* context is {@link #run(ContextConsumer) run} and restored when the context is
* closed.
* @param pairs the key-value pairs for properties that need to be added to the system
* @return a new instance with the updated system properties
* @see TestPropertyValues
* @see #withSystemProperties(String...)
*/
public SELF withSystemProperties(String... pairs) {
return newInstance(this.contextFactory, this.environmentProperties,
this.systemProperties.and(pairs), this.classLoader, this.parent,
this.configurations);
}
/**
* Customize the {@link ClassLoader} that the {@link ApplicationContext} should use.
* Customizing the {@link ClassLoader} is an effective manner to hide resources from
* the classpath.
* @param classLoader the classloader to use (can be null to use the default)
* @return a new instance with the updated class loader
* @see HidePackagesClassLoader
*/
public SELF withClassLoader(ClassLoader classLoader) {
return newInstance(this.contextFactory, this.environmentProperties,
this.systemProperties, classLoader, this.parent, this.configurations);
}
/**
* Configure the {@link ConfigurableApplicationContext#setParent(ApplicationContext)
* parent} of the {@link ApplicationContext}.
* @param parent the parent
* @return a new instance with the updated parent
*/
public SELF withParent(ApplicationContext parent) {
return newInstance(this.contextFactory, this.environmentProperties,
this.systemProperties, this.classLoader, parent, this.configurations);
}
/**
* Register the specified user configuration classes with the
* {@link ApplicationContext}.
* @param configurationClasses the user configuration classes to add
* @return a new instance with the updated configuration
*/
public SELF withUserConfiguration(Class<?>... configurationClasses) {
return withConfiguration(UserConfigurations.of(configurationClasses));
}
/**
* Register the specified configuration classes with the {@link ApplicationContext}.
* @param configurations the configurations to add
* @return a new instance with the updated configuration
*/
public SELF withConfiguration(Configurations configurations) {
Assert.notNull(configurations, "Configurations must not be null");
return newInstance(this.contextFactory, this.environmentProperties,
this.systemProperties, this.classLoader, this.parent,
add(this.configurations, configurations));
}
private <T> List<T> add(List<T> list, T element) {
List<T> result = new ArrayList<>(list);
result.add(element);
return result;
}
protected abstract SELF newInstance(Supplier<C> contextFactory,
TestPropertyValues environmentProperties, TestPropertyValues systemProperties,
ClassLoader classLoader, ApplicationContext parent,
List<Configurations> configurations);
/**
* Create and refresh a new {@link ApplicationContext} based on the current state of
* this loader. The context is consumed by the specified {@code consumer} and closed
* upon completion.
* @param consumer the consumer of the created {@link ApplicationContext}
* @return this instance
*/
@SuppressWarnings("unchecked")
public SELF run(ContextConsumer<? super A> consumer) {
this.systemProperties.applyToSystemProperties(() -> {
try (A context = createAssertableContext()) {
accept(consumer, context);
}
return null;
});
return (SELF) this;
}
@SuppressWarnings("unchecked")
private A createAssertableContext() {
ResolvableType resolvableType = ResolvableType
.forClass(AbstractApplicationContextRunner.class, getClass());
Class<A> assertType = (Class<A>) resolvableType.resolveGeneric(1);
Class<C> contextType = (Class<C>) resolvableType.resolveGeneric(2);
return ApplicationContextAssertProvider.get(assertType, contextType,
this::createAndLoadContext);
}
private C createAndLoadContext() {
C context = this.contextFactory.get();
try {
configureContext(context);
return context;
}
catch (RuntimeException ex) {
context.close();
throw ex;
}
}
private void configureContext(C context) {
if (this.parent != null) {
context.setParent(this.parent);
}
if (this.classLoader != null) {
Assert.isInstanceOf(DefaultResourceLoader.class, context);
((DefaultResourceLoader) context).setClassLoader(this.classLoader);
}
this.environmentProperties.applyTo(context);
Class<?>[] classes = Configurations.getClasses(this.configurations);
if (classes.length > 0) {
((AnnotationConfigRegistry) context).register(classes);
}
context.refresh();
}
private void accept(ContextConsumer<? super A> consumer, A context) {
try {
consumer.accept(context);
}
catch (Throwable ex) {
rethrow(ex);
}
}
@SuppressWarnings("unchecked")
private <E extends Throwable> void rethrow(Throwable e) throws E {
throw (E) e;
}
}

View File

@@ -0,0 +1,80 @@
/*
* 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.test.context.runner;
import java.util.List;
import java.util.function.Supplier;
import org.springframework.boot.context.annotation.Configurations;
import org.springframework.boot.test.context.assertj.AssertableApplicationContext;
import org.springframework.boot.test.util.TestPropertyValues;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
/**
* A {@link AbstractApplicationContextRunner ApplicationContext runner} for a standard,
* non-web environment {@link ConfigurableApplicationContext}.
* <p>
* See {@link AbstractApplicationContextRunner} for details.
*
* @author Stephane Nicoll
* @author Andy Wilkinson
* @author Phillip Webb
* @since 2.0.0
*/
public class ApplicationContextRunner extends
AbstractApplicationContextRunner<ApplicationContextRunner, ConfigurableApplicationContext, AssertableApplicationContext> {
/**
* Create a new {@link ApplicationContextRunner} instance using an
* {@link AnnotationConfigApplicationContext} as the underlying source.
*/
public ApplicationContextRunner() {
this(AnnotationConfigApplicationContext::new);
}
/**
* Create a new {@link ApplicationContextRunner} instance using the specified
* {@code contextFactory} as the underlying source.
* @param contextFactory a supplier that returns a new instance on each call
*/
public ApplicationContextRunner(
Supplier<ConfigurableApplicationContext> contextFactory) {
super(contextFactory);
}
private ApplicationContextRunner(
Supplier<ConfigurableApplicationContext> contextFactory,
TestPropertyValues environmentProperties, TestPropertyValues systemProperties,
ClassLoader classLoader, ApplicationContext parent,
List<Configurations> configurations) {
super(contextFactory, environmentProperties, systemProperties, classLoader,
parent, configurations);
}
@Override
protected ApplicationContextRunner newInstance(
Supplier<ConfigurableApplicationContext> contextFactory,
TestPropertyValues environmentProperties, TestPropertyValues systemProperties,
ClassLoader classLoader, ApplicationContext parent,
List<Configurations> configurations) {
return new ApplicationContextRunner(contextFactory, environmentProperties,
systemProperties, classLoader, parent, configurations);
}
}

View File

@@ -0,0 +1,41 @@
/*
* 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.test.context.runner;
import org.springframework.context.ApplicationContext;
/**
* Callback interface used to process an {@link ApplicationContext} with the ability to
* throw a (checked) exception.
*
* @author Stephane Nicoll
* @author Andy Wilkinson
* @param <C> The application context type
* @since 2.0.0
* @see AbstractApplicationContextRunner
*/
@FunctionalInterface
public interface ContextConsumer<C extends ApplicationContext> {
/**
* Performs this operation on the supplied {@code context}.
* @param context the application context to consume
* @throws Throwable any exception that might occur in assertions
*/
void accept(C context) throws Throwable;
}

View File

@@ -0,0 +1,81 @@
/*
* 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.test.context.runner;
import java.util.List;
import java.util.function.Supplier;
import org.springframework.boot.context.annotation.Configurations;
import org.springframework.boot.test.context.assertj.AssertableReactiveWebApplicationContext;
import org.springframework.boot.test.util.TestPropertyValues;
import org.springframework.boot.web.reactive.context.ConfigurableReactiveWebApplicationContext;
import org.springframework.boot.web.reactive.context.GenericReactiveWebApplicationContext;
import org.springframework.context.ApplicationContext;
/**
* A {@link AbstractApplicationContextRunner ApplicationContext runner} for a
* {@link ConfigurableReactiveWebApplicationContext}.
* <p>
* See {@link AbstractApplicationContextRunner} for details.
*
* @author Andy Wilkinson
* @author Stephane Nicoll
* @author Phillip Webb
* @since 2.0.0
*/
public final class ReactiveWebApplicationContextRunner extends
AbstractApplicationContextRunner<ReactiveWebApplicationContextRunner, ConfigurableReactiveWebApplicationContext, AssertableReactiveWebApplicationContext> {
/**
* Create a new {@link ReactiveWebApplicationContextRunner} instance using a
* {@link GenericReactiveWebApplicationContext} as the underlying source.
*/
public ReactiveWebApplicationContextRunner() {
this(GenericReactiveWebApplicationContext::new);
}
/**
* Create a new {@link ApplicationContextRunner} instance using the specified
* {@code contextFactory} as the underlying source.
* @param contextFactory a supplier that returns a new instance on each call
*/
public ReactiveWebApplicationContextRunner(
Supplier<ConfigurableReactiveWebApplicationContext> contextFactory) {
super(contextFactory);
}
private ReactiveWebApplicationContextRunner(
Supplier<ConfigurableReactiveWebApplicationContext> contextFactory,
TestPropertyValues environmentProperties, TestPropertyValues systemProperties,
ClassLoader classLoader, ApplicationContext parent,
List<Configurations> configurations) {
super(contextFactory, environmentProperties, systemProperties, classLoader,
parent, configurations);
}
@Override
protected ReactiveWebApplicationContextRunner newInstance(
Supplier<ConfigurableReactiveWebApplicationContext> contextFactory,
TestPropertyValues environmentProperties, TestPropertyValues systemProperties,
ClassLoader classLoader, ApplicationContext parent,
List<Configurations> configurations) {
return new ReactiveWebApplicationContextRunner(contextFactory,
environmentProperties, systemProperties, classLoader, parent,
configurations);
}
}

View File

@@ -0,0 +1,99 @@
/*
* 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.test.context.runner;
import java.util.List;
import java.util.function.Supplier;
import org.springframework.boot.context.annotation.Configurations;
import org.springframework.boot.test.context.assertj.AssertableWebApplicationContext;
import org.springframework.boot.test.util.TestPropertyValues;
import org.springframework.context.ApplicationContext;
import org.springframework.mock.web.MockServletContext;
import org.springframework.web.context.ConfigurableWebApplicationContext;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
/**
* A {@link AbstractApplicationContextRunner ApplicationContext runner} for a Servlet
* based {@link ConfigurableWebApplicationContext}.
* <p>
* See {@link AbstractApplicationContextRunner} for details.
*
* @author Andy Wilkinson
* @author Stephane Nicoll
* @author Phillip Webb
* @since 2.0.0
*/
public final class WebApplicationContextRunner extends
AbstractApplicationContextRunner<WebApplicationContextRunner, ConfigurableWebApplicationContext, AssertableWebApplicationContext> {
/**
* Create a new {@link WebApplicationContextRunner} instance using an
* {@link AnnotationConfigWebApplicationContext} with a {@link MockServletContext} as
* the underlying source.
* @see #withMockServletContext(Supplier)
*/
public WebApplicationContextRunner() {
this(withMockServletContext(AnnotationConfigWebApplicationContext::new));
}
/**
* Create a new {@link WebApplicationContextRunner} instance using the specified
* {@code contextFactory} as the underlying source.
* @param contextFactory a supplier that returns a new instance on each call
*/
public WebApplicationContextRunner(
Supplier<ConfigurableWebApplicationContext> contextFactory) {
super(contextFactory);
}
private WebApplicationContextRunner(
Supplier<ConfigurableWebApplicationContext> contextFactory,
TestPropertyValues environmentProperties, TestPropertyValues systemProperties,
ClassLoader classLoader, ApplicationContext parent,
List<Configurations> configurations) {
super(contextFactory, environmentProperties, systemProperties, classLoader,
parent, configurations);
}
@Override
protected WebApplicationContextRunner newInstance(
Supplier<ConfigurableWebApplicationContext> contextFactory,
TestPropertyValues environmentProperties, TestPropertyValues systemProperties,
ClassLoader classLoader, ApplicationContext parent,
List<Configurations> configurations) {
return new WebApplicationContextRunner(contextFactory, environmentProperties,
systemProperties, classLoader, parent, configurations);
}
/**
* Decorate the specified {@code contextFactory} to set a {@link MockServletContext}
* on each newly created {@link WebApplicationContext}.
* @param contextFactory the context factory to decorate
* @return an updated supplier that will set the {@link MockServletContext}
*/
public static Supplier<ConfigurableWebApplicationContext> withMockServletContext(
Supplier<ConfigurableWebApplicationContext> contextFactory) {
return (contextFactory == null ? null : () -> {
ConfigurableWebApplicationContext context = contextFactory.get();
context.setServletContext(new MockServletContext());
return context;
});
}
}

View File

@@ -0,0 +1,20 @@
/*
* 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.
*/
/**
* Test utilities to run application contexts for testing.
*/
package org.springframework.boot.test.context.runner;

View File

@@ -0,0 +1,404 @@
/*
* 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.test.json;
import java.io.BufferedReader;
import java.io.Closeable;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.io.StringReader;
import java.lang.reflect.Field;
import org.assertj.core.api.Assertions;
import org.springframework.beans.factory.ObjectFactory;
import org.springframework.core.ResolvableType;
import org.springframework.core.io.ByteArrayResource;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.FileSystemResource;
import org.springframework.core.io.InputStreamResource;
import org.springframework.core.io.Resource;
import org.springframework.util.Assert;
import org.springframework.util.ReflectionUtils;
/**
* Base class for AssertJ based JSON marshal testers. Exposes specific Asserts following a
* {@code read}, {@code write} or {@code parse} of JSON content. Typically used in
* combination with an AssertJ {@link Assertions#assertThat(Object) assertThat} call. For
* example: <pre class="code">
* public class ExampleObjectJsonTests {
*
* private AbstractJsonTester&lt;ExampleObject&gt; json = //...
*
* &#064;Test
* public void testWriteJson() {
* ExampleObject object = //...
* assertThat(json.write(object)).isEqualToJson("expected.json");
* assertThat(json.read("expected.json")).isEqualTo(object);
* }
*
* }
* </pre> For a complete list of supported assertions see {@link JsonContentAssert} and
* {@link ObjectContentAssert}.
* <p>
* To use this library JSONAssert must be on the test classpath.
*
* @param <T> the type under test
* @author Phillip Webb
* @since 1.4.0
* @see JsonContentAssert
* @see ObjectContentAssert
*/
public abstract class AbstractJsonMarshalTester<T> {
private Class<?> resourceLoadClass;
private ResolvableType type;
/**
* Create a new uninitialized {@link AbstractJsonMarshalTester} instance.
*/
protected AbstractJsonMarshalTester() {
}
/**
* Create a new {@link AbstractJsonMarshalTester} instance.
* @param resourceLoadClass the source class used when loading relative classpath
* resources
* @param type the type under test
*/
public AbstractJsonMarshalTester(Class<?> resourceLoadClass, ResolvableType type) {
Assert.notNull(resourceLoadClass, "ResourceLoadClass must not be null");
Assert.notNull(type, "Type must not be null");
initialize(resourceLoadClass, type);
}
/**
* Initialize the marshal tester for use.
* @param resourceLoadClass the source class used when loading relative classpath
* resources
* @param type the type under test
*/
protected final void initialize(Class<?> resourceLoadClass, ResolvableType type) {
if (this.resourceLoadClass == null && this.type == null) {
this.resourceLoadClass = resourceLoadClass;
this.type = type;
}
}
/**
* Return the type under test.
* @return the type under test
*/
protected final ResolvableType getType() {
return this.type;
}
/**
* Return class used to load relative resources.
* @return the resource load class
*/
protected final Class<?> getResourceLoadClass() {
return this.resourceLoadClass;
}
/**
* Return {@link JsonContent} from writing the specific value.
* @param value the value to write
* @return the {@link JsonContent}
* @throws IOException on write error
*/
public JsonContent<T> write(T value) throws IOException {
verify();
Assert.notNull(value, "Value must not be null");
String json = writeObject(value, this.type);
return new JsonContent<>(this.resourceLoadClass, this.type, json);
}
/**
* Return the object created from parsing the specific JSON bytes.
* @param jsonBytes the source JSON bytes
* @return the resulting object
* @throws IOException on parse error
*/
public T parseObject(byte[] jsonBytes) throws IOException {
verify();
return parse(jsonBytes).getObject();
}
/**
* Return {@link ObjectContent} from parsing the specific JSON bytes.
* @param jsonBytes the source JSON bytes
* @return the {@link ObjectContent}
* @throws IOException on parse error
*/
public ObjectContent<T> parse(byte[] jsonBytes) throws IOException {
verify();
Assert.notNull(jsonBytes, "JsonBytes must not be null");
return read(new ByteArrayResource(jsonBytes));
}
/**
* Return the object created from parsing the specific JSON String.
* @param jsonString the source JSON string
* @return the resulting object
* @throws IOException on parse error
*/
public T parseObject(String jsonString) throws IOException {
verify();
return parse(jsonString).getObject();
}
/**
* Return {@link ObjectContent} from parsing the specific JSON String.
* @param jsonString the source JSON string
* @return the {@link ObjectContent}
* @throws IOException on parse error
*/
public ObjectContent<T> parse(String jsonString) throws IOException {
verify();
Assert.notNull(jsonString, "JsonString must not be null");
return read(new StringReader(jsonString));
}
/**
* Return the object created from reading from the specified classpath resource.
* @param resourcePath the source resource path. May be a full path or a path relative
* to the {@code resourceLoadClass} passed to the constructor
* @return the resulting object
* @throws IOException on read error
*/
public T readObject(String resourcePath) throws IOException {
verify();
return read(resourcePath).getObject();
}
/**
* Return {@link ObjectContent} from reading from the specified classpath resource.
* @param resourcePath the source resource path. May be a full path or a path relative
* to the {@code resourceLoadClass} passed to the constructor
* @return the {@link ObjectContent}
* @throws IOException on read error
*/
public ObjectContent<T> read(String resourcePath) throws IOException {
verify();
Assert.notNull(resourcePath, "ResourcePath must not be null");
return read(new ClassPathResource(resourcePath, this.resourceLoadClass));
}
/**
* Return the object created from reading from the specified file.
* @param file the source file
* @return the resulting object
* @throws IOException on read error
*/
public T readObject(File file) throws IOException {
verify();
return read(file).getObject();
}
/**
* Return {@link ObjectContent} from reading from the specified file.
* @param file the source file
* @return the {@link ObjectContent}
* @throws IOException on read error
*/
public ObjectContent<T> read(File file) throws IOException {
verify();
Assert.notNull(file, "File must not be null");
return read(new FileSystemResource(file));
}
/**
* Return the object created from reading from the specified input stream.
* @param inputStream the source input stream
* @return the resulting object
* @throws IOException on read error
*/
public T readObject(InputStream inputStream) throws IOException {
verify();
return read(inputStream).getObject();
}
/**
* Return {@link ObjectContent} from reading from the specified input stream.
* @param inputStream the source input stream
* @return the {@link ObjectContent}
* @throws IOException on read error
*/
public ObjectContent<T> read(InputStream inputStream) throws IOException {
verify();
Assert.notNull(inputStream, "InputStream must not be null");
return read(new InputStreamResource(inputStream));
}
/**
* Return the object created from reading from the specified resource.
* @param resource the source resource
* @return the resulting object
* @throws IOException on read error
*/
public T readObject(Resource resource) throws IOException {
verify();
return read(resource).getObject();
}
/**
* Return {@link ObjectContent} from reading from the specified resource.
* @param resource the source resource
* @return the {@link ObjectContent}
* @throws IOException on read error
*/
public ObjectContent<T> read(Resource resource) throws IOException {
verify();
Assert.notNull(resource, "Resource must not be null");
InputStream inputStream = resource.getInputStream();
T object = readObject(inputStream, this.type);
closeQuietly(inputStream);
return new ObjectContent<>(this.type, object);
}
/**
* Return the object created from reading from the specified reader.
* @param reader the source reader
* @return the resulting object
* @throws IOException on read error
*/
public T readObject(Reader reader) throws IOException {
verify();
return read(reader).getObject();
}
/**
* Return {@link ObjectContent} from reading from the specified reader.
* @param reader the source reader
* @return the {@link ObjectContent}
* @throws IOException on read error
*/
public ObjectContent<T> read(Reader reader) throws IOException {
verify();
Assert.notNull(reader, "Reader must not be null");
T object = readObject(reader, this.type);
closeQuietly(reader);
return new ObjectContent<>(this.type, object);
}
private void closeQuietly(Closeable closeable) {
try {
closeable.close();
}
catch (IOException ex) {
}
}
private void verify() {
Assert.state(this.resourceLoadClass != null,
"Uninitialized JsonMarshalTester (ResourceLoadClass is null)");
Assert.state(this.type != null, "Uninitialized JsonMarshalTester (Type is null)");
}
/**
* Write the specified object to a JSON string.
* @param value the source value (never {@code null})
* @param type the resulting type (never {@code null})
* @return the JSON string
* @throws IOException on write error
*/
protected abstract String writeObject(T value, ResolvableType type)
throws IOException;
/**
* Read from the specified input stream to create an object of the specified type. The
* default implementation delegates to {@link #readObject(Reader, ResolvableType)}.
* @param inputStream the source input stream (never {@code null})
* @param type the resulting type (never {@code null})
* @return the resulting object
* @throws IOException on read error
*/
protected T readObject(InputStream inputStream, ResolvableType type)
throws IOException {
BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
return readObject(reader, type);
}
/**
* Read from the specified reader to create an object of the specified type.
* @param reader the source reader (never {@code null})
* @param type the resulting type (never {@code null})
* @return the resulting object
* @throws IOException on read error
*/
protected abstract T readObject(Reader reader, ResolvableType type)
throws IOException;
/**
* Utility class used to support field initialization. Used by subclasses to support
* {@code initFields}.
*
* @param <M> The marshaller type
*/
protected static abstract class FieldInitializer<M> {
private final Class<?> testerClass;
@SuppressWarnings("rawtypes")
protected FieldInitializer(
Class<? extends AbstractJsonMarshalTester> testerClass) {
Assert.notNull(testerClass, "TesterClass must not be null");
this.testerClass = testerClass;
}
public void initFields(final Object testInstance, final M marshaller) {
Assert.notNull(testInstance, "TestInstance must not be null");
Assert.notNull(marshaller, "Marshaller must not be null");
initFields(testInstance, () -> marshaller);
}
public void initFields(final Object testInstance,
final ObjectFactory<M> marshaller) {
Assert.notNull(testInstance, "TestInstance must not be null");
Assert.notNull(marshaller, "Marshaller must not be null");
ReflectionUtils.doWithFields(testInstance.getClass(),
(field) -> doWithField(field, testInstance, marshaller));
}
protected void doWithField(Field field, Object test,
ObjectFactory<M> marshaller) {
if (this.testerClass.isAssignableFrom(field.getType())) {
ReflectionUtils.makeAccessible(field);
Object existingValue = ReflectionUtils.getField(field, test);
if (existingValue == null) {
setupField(field, test, marshaller);
}
}
}
private void setupField(Field field, Object test, ObjectFactory<M> marshaller) {
ResolvableType type = ResolvableType.forField(field).getGeneric();
ReflectionUtils.setField(field, test,
createTester(test.getClass(), type, marshaller.getObject()));
}
protected abstract AbstractJsonMarshalTester<Object> createTester(
Class<?> resourceLoadClass, ResolvableType type, M marshaller);
}
}

View File

@@ -0,0 +1,175 @@
/*
* 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.test.json;
import java.io.File;
import java.io.InputStream;
import java.nio.charset.Charset;
import org.springframework.core.ResolvableType;
import org.springframework.core.io.Resource;
import org.springframework.util.Assert;
/**
* AssertJ based JSON tester that works with basic JSON strings. Allows testing of JSON
* payloads created from any source, for example:<pre class="code">
* public class ExampleObjectJsonTests {
*
* private BasicJsonTester json = new BasicJsonTester(getClass());
*
* &#064;Test
* public void testWriteJson() throws IOException {
* assertThat(json.from("example.json")).extractingJsonPathStringValue("@.name")
.isEqualTo("Spring");
* }
*
* }
* </pre>
*
* See {@link AbstractJsonMarshalTester} for more details.
*
* @author Phillip Webb
* @author Andy Wilkinson
* @since 1.4.0
*/
public class BasicJsonTester {
private JsonLoader loader;
/**
* Create a new uninitialized {@link BasicJsonTester} instance.
*/
protected BasicJsonTester() {
}
/**
* Create a new {@link BasicJsonTester} instance that will load resources as UTF-8.
* @param resourceLoadClass the source class used to load resources
*/
public BasicJsonTester(Class<?> resourceLoadClass) {
this(resourceLoadClass, null);
}
/**
* Create a new {@link BasicJsonTester} instance.
* @param resourceLoadClass the source class used to load resources
* @param charset the charset used to load resources
* @since 1.4.1
*/
public BasicJsonTester(Class<?> resourceLoadClass, Charset charset) {
Assert.notNull(resourceLoadClass, "ResourceLoadClass must not be null");
this.loader = new JsonLoader(resourceLoadClass, charset);
}
/**
* Initialize the marshal tester for use, configuring it to load JSON resources as
* UTF-8.
* @param resourceLoadClass the source class used when loading relative classpath
* resources
* @param type the type under test
*/
protected final void initialize(Class<?> resourceLoadClass, ResolvableType type) {
this.initialize(resourceLoadClass, null, type);
}
/**
* Initialize the marshal tester for use.
* @param resourceLoadClass the source class used when loading relative classpath
* resources
* @param charset the charset used when loading relative classpath resources
* @param type the type under test
* @since 1.4.1
*/
protected final void initialize(Class<?> resourceLoadClass, Charset charset,
ResolvableType type) {
if (this.loader == null) {
this.loader = new JsonLoader(resourceLoadClass, charset);
}
}
/**
* Create JSON content from the specified String source. The source can contain the
* JSON itself or, if it ends with {@code .json}, the name of a resource to be loaded
* using {@code resourceLoadClass}.
* @param source JSON content or a {@code .json} resource name
* @return the JSON content
*/
public JsonContent<Object> from(CharSequence source) {
verify();
return getJsonContent(this.loader.getJson(source));
}
/**
* Create JSON content from the specified resource path.
* @param path the path of the resource to load
* @param resourceLoadClass the source class used to load the resource
* @return the JSON content
*/
public JsonContent<Object> from(String path, Class<?> resourceLoadClass) {
verify();
return getJsonContent(this.loader.getJson(path, resourceLoadClass));
}
/**
* Create JSON content from the specified JSON bytes.
* @param source the bytes of JSON
* @return the JSON content
*/
public JsonContent<Object> from(byte[] source) {
verify();
return getJsonContent(this.loader.getJson(source));
}
/**
* Create JSON content from the specified JSON file.
* @param source the file containing JSON
* @return the JSON content
*/
public JsonContent<Object> from(File source) {
verify();
return getJsonContent(this.loader.getJson(source));
}
/**
* Create JSON content from the specified JSON input stream.
* @param source the input stream containing JSON
* @return the JSON content
*/
public JsonContent<Object> from(InputStream source) {
verify();
return getJsonContent(this.loader.getJson(source));
}
/**
* Create JSON content from the specified JSON resource.
* @param source the resource containing JSON
* @return the JSON content
*/
public JsonContent<Object> from(Resource source) {
verify();
return getJsonContent(this.loader.getJson(source));
}
private void verify() {
Assert.state(this.loader != null, "Uninitialized BasicJsonTester");
}
private JsonContent<Object> getJsonContent(String json) {
return new JsonContent<>(this.loader.getResourceLoadClass(), null, json);
}
}

View File

@@ -0,0 +1,103 @@
/*
* 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.test.json;
import java.net.URL;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.test.context.ContextConfigurationAttributes;
import org.springframework.test.context.ContextCustomizer;
import org.springframework.test.context.ContextCustomizerFactory;
import org.springframework.test.context.MergedContextConfiguration;
/**
* A {@link ContextCustomizerFactory} that produces a {@link ContextCustomizer} that warns
* the user when multiple occurrences of {@code JSONObject} are found on the class path.
*
* @author Andy Wilkinson
*/
class DuplicateJsonObjectContextCustomizerFactory implements ContextCustomizerFactory {
@Override
public ContextCustomizer createContextCustomizer(Class<?> testClass,
List<ContextConfigurationAttributes> configAttributes) {
return new DuplicateJsonObjectContextCustomizer();
}
private static class DuplicateJsonObjectContextCustomizer
implements ContextCustomizer {
private final Log logger = LogFactory
.getLog(DuplicateJsonObjectContextCustomizer.class);
@Override
public void customizeContext(ConfigurableApplicationContext context,
MergedContextConfiguration mergedConfig) {
List<URL> jsonObjects = findJsonObjects();
if (jsonObjects.size() > 1) {
logDuplicateJsonObjectsWarning(jsonObjects);
}
}
private List<URL> findJsonObjects() {
List<URL> jsonObjects = new ArrayList<>();
try {
Enumeration<URL> resources = getClass().getClassLoader()
.getResources("org/json/JSONObject.class");
while (resources.hasMoreElements()) {
jsonObjects.add(resources.nextElement());
}
}
catch (Exception ex) {
// Continue
}
return jsonObjects;
}
private void logDuplicateJsonObjectsWarning(List<URL> jsonObjects) {
StringBuilder message = new StringBuilder("\n\nFound multiple occurrences of"
+ " org.json.JSONObject on the class path:\n\n");
for (URL jsonObject : jsonObjects) {
message.append("\t" + jsonObject + "\n");
}
message.append("\nYou may wish to exclude one of them to ensure"
+ " predictable runtime behaviour\n");
this.logger.warn(message);
}
@Override
public int hashCode() {
return getClass().hashCode();
}
@Override
public boolean equals(Object obj) {
if (obj == null || obj.getClass() != getClass()) {
return false;
}
return true;
}
}
}

View File

@@ -0,0 +1,129 @@
/*
* 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.test.json;
import java.io.IOException;
import java.io.Reader;
import com.google.gson.Gson;
import org.springframework.beans.factory.ObjectFactory;
import org.springframework.core.ResolvableType;
import org.springframework.util.Assert;
/**
* AssertJ based JSON tester backed by Gson. Usually instantiated via
* {@link #initFields(Object, Gson)}, for example: <pre class="code">
* public class ExampleObjectJsonTests {
*
* private GsonTester&lt;ExampleObject&gt; json;
*
* &#064;Before
* public void setup() {
* Gson gson = new GsonBuilder().create();
* GsonTester.initFields(this, gson);
* }
*
* &#064;Test
* public void testWriteJson() throws IOException {
* ExampleObject object = //...
* assertThat(json.write(object)).isEqualToJson("expected.json");
* }
*
* }
* </pre>
*
* See {@link AbstractJsonMarshalTester} for more details.
*
* @param <T> the type under test
* @author Phillip Webb
* @since 1.4.0
*/
public class GsonTester<T> extends AbstractJsonMarshalTester<T> {
private final Gson gson;
/**
* Create a new uninitialized {@link GsonTester} instance.
* @param gson the Gson instance
*/
protected GsonTester(Gson gson) {
Assert.notNull(gson, "Gson must not be null");
this.gson = gson;
}
/**
* Create a new {@link GsonTester} instance.
* @param resourceLoadClass the source class used to load resources
* @param type the type under test
* @param gson the Gson instance
* @see #initFields(Object, Gson)
*/
public GsonTester(Class<?> resourceLoadClass, ResolvableType type, Gson gson) {
super(resourceLoadClass, type);
Assert.notNull(gson, "Gson must not be null");
this.gson = gson;
}
@Override
protected String writeObject(T value, ResolvableType type) throws IOException {
return this.gson.toJson(value, type.getType());
}
@Override
protected T readObject(Reader reader, ResolvableType type) throws IOException {
return this.gson.fromJson(reader, type.getType());
}
/**
* Utility method to initialize {@link GsonTester} fields. See {@link GsonTester
* class-level documentation} for example usage.
* @param testInstance the test instance
* @param gson the Gson instance
*/
public static void initFields(Object testInstance, Gson gson) {
new GsonFieldInitializer().initFields(testInstance, gson);
}
/**
* Utility method to initialize {@link GsonTester} fields. See {@link GsonTester
* class-level documentation} for example usage.
* @param testInstance the test instance
* @param gson an object factory to create the Gson instance
*/
public static void initFields(Object testInstance, ObjectFactory<Gson> gson) {
new GsonFieldInitializer().initFields(testInstance, gson);
}
/**
* {@link FieldInitializer} for Gson.
*/
private static class GsonFieldInitializer extends FieldInitializer<Gson> {
protected GsonFieldInitializer() {
super(GsonTester.class);
}
@Override
protected AbstractJsonMarshalTester<Object> createTester(
Class<?> resourceLoadClass, ResolvableType type, Gson marshaller) {
return new GsonTester<>(resourceLoadClass, type, marshaller);
}
}
}

View File

@@ -0,0 +1,183 @@
/*
* 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.test.json;
import java.io.IOException;
import java.io.InputStream;
import java.io.Reader;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.ObjectReader;
import com.fasterxml.jackson.databind.ObjectWriter;
import org.springframework.beans.factory.ObjectFactory;
import org.springframework.core.ResolvableType;
import org.springframework.util.Assert;
/**
* AssertJ based JSON tester backed by Jackson. Usually instantiated via
* {@link #initFields(Object, ObjectMapper)}, for example: <pre class="code">
* public class ExampleObjectJsonTests {
*
* private JacksonTester&lt;ExampleObject&gt; json;
*
* &#064;Before
* public void setup() {
* ObjectMapper objectMapper = new ObjectMapper();
* JacksonTester.initFields(this, objectMapper);
* }
*
* &#064;Test
* public void testWriteJson() throws IOException {
* ExampleObject object = //...
* assertThat(json.write(object)).isEqualToJson("expected.json");
* }
*
* }
* </pre>
*
* See {@link AbstractJsonMarshalTester} for more details.
*
* @param <T> the type under test
* @author Phillip Webb
* @author Madhura Bhave
* @since 1.4.0
*/
public class JacksonTester<T> extends AbstractJsonMarshalTester<T> {
private final ObjectMapper objectMapper;
private Class<?> view;
/**
* Create a new {@link JacksonTester} instance.
* @param objectMapper the Jackson object mapper
*/
protected JacksonTester(ObjectMapper objectMapper) {
Assert.notNull(objectMapper, "ObjectMapper must not be null");
this.objectMapper = objectMapper;
}
/**
* Create a new {@link JacksonTester} instance.
* @param resourceLoadClass the source class used to load resources
* @param type the type under test
* @param objectMapper the Jackson object mapper
*/
public JacksonTester(Class<?> resourceLoadClass, ResolvableType type,
ObjectMapper objectMapper) {
this(resourceLoadClass, type, objectMapper, null);
}
public JacksonTester(Class<?> resourceLoadClass, ResolvableType type,
ObjectMapper objectMapper, Class<?> view) {
super(resourceLoadClass, type);
Assert.notNull(objectMapper, "ObjectMapper must not be null");
this.objectMapper = objectMapper;
this.view = view;
}
@Override
protected T readObject(InputStream inputStream, ResolvableType type)
throws IOException {
return getObjectReader(type).readValue(inputStream);
}
@Override
protected T readObject(Reader reader, ResolvableType type) throws IOException {
return getObjectReader(type).readValue(reader);
}
private ObjectReader getObjectReader(ResolvableType type) {
ObjectReader objectReader = this.objectMapper.readerFor(getType(type));
if (this.view != null) {
return objectReader.withView(this.view);
}
return objectReader;
}
@Override
protected String writeObject(T value, ResolvableType type) throws IOException {
return getObjectWriter(type).writeValueAsString(value);
}
private ObjectWriter getObjectWriter(ResolvableType type) {
ObjectWriter objectWriter = this.objectMapper.writerFor(getType(type));
if (this.view != null) {
return objectWriter.withView(this.view);
}
return objectWriter;
}
private JavaType getType(ResolvableType type) {
return this.objectMapper.constructType(type.getType());
}
/**
* Utility method to initialize {@link JacksonTester} fields. See {@link JacksonTester
* class-level documentation} for example usage.
* @param testInstance the test instance
* @param objectMapper the object mapper
* @see #initFields(Object, ObjectMapper)
*/
public static void initFields(Object testInstance, ObjectMapper objectMapper) {
new JacksonFieldInitializer().initFields(testInstance, objectMapper);
}
/**
* Utility method to initialize {@link JacksonTester} fields. See {@link JacksonTester
* class-level documentation} for example usage.
* @param testInstance the test instance
* @param objectMapperFactory a factory to create the object mapper
* @see #initFields(Object, ObjectMapper)
*/
public static void initFields(Object testInstance,
ObjectFactory<ObjectMapper> objectMapperFactory) {
new JacksonFieldInitializer().initFields(testInstance, objectMapperFactory);
}
/**
* Returns a new instance of {@link JacksonTester} with the view that should be used
* for json serialization/deserialization.
* @param view the view class
* @return the new instance
*/
public JacksonTester<T> forView(Class<?> view) {
return new JacksonTester<>(this.getResourceLoadClass(), this.getType(),
this.objectMapper, view);
}
/**
* {@link FieldInitializer} for Jackson.
*/
private static class JacksonFieldInitializer extends FieldInitializer<ObjectMapper> {
protected JacksonFieldInitializer() {
super(JacksonTester.class);
}
@Override
protected AbstractJsonMarshalTester<Object> createTester(
Class<?> resourceLoadClass, ResolvableType type,
ObjectMapper marshaller) {
return new JacksonTester<>(resourceLoadClass, type, marshaller);
}
}
}

View File

@@ -0,0 +1,82 @@
/*
* 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.test.json;
import org.assertj.core.api.AssertProvider;
import org.springframework.core.ResolvableType;
import org.springframework.util.Assert;
/**
* JSON content created usually from a JSON tester. Generally used only to
* {@link AssertProvider provide} {@link JsonContentAssert} to AssertJ {@code assertThat}
* calls.
*
* @param <T> the source type that created the content
* @author Phillip Webb
* @since 1.4.0
*/
public final class JsonContent<T> implements AssertProvider<JsonContentAssert> {
private final Class<?> resourceLoadClass;
private final ResolvableType type;
private final String json;
/**
* Create a new {@link JsonContent} instance.
* @param resourceLoadClass the source class used to load resources
* @param type the type under test (or {@code null} if not known)
* @param json the actual JSON content
*/
public JsonContent(Class<?> resourceLoadClass, ResolvableType type, String json) {
Assert.notNull(resourceLoadClass, "ResourceLoadClass must not be null");
Assert.notNull(json, "JSON must not be null");
this.resourceLoadClass = resourceLoadClass;
this.type = type;
this.json = json;
}
/**
* Use AssertJ's {@link org.assertj.core.api.Assertions#assertThat assertThat}
* instead.
*
* @deprecated in favor of AssertJ's {@link org.assertj.core.api.Assertions#assertThat
* assertThat}
*/
@Override
@Deprecated
public JsonContentAssert assertThat() {
return new JsonContentAssert(this.resourceLoadClass, this.json);
}
/**
* Return the actual JSON content string.
* @return the JSON content
*/
public String getJson() {
return this.json;
}
@Override
public String toString() {
return "JsonContent " + this.json
+ (this.type == null ? "" : " created from " + this.type);
}
}

View File

@@ -0,0 +1,99 @@
/*
* Copyright 2012-2016 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.test.json;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.charset.Charset;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import org.springframework.util.FileCopyUtils;
/**
* Internal helper used to load JSON from various sources.
*
* @author Phillip Webb
* @author Andy Wilkinson
*/
class JsonLoader {
private final Class<?> resourceLoadClass;
private final Charset charset;
JsonLoader(Class<?> resourceLoadClass, Charset charset) {
this.resourceLoadClass = resourceLoadClass;
this.charset = charset == null ? Charset.forName("UTF-8") : charset;
}
Class<?> getResourceLoadClass() {
return this.resourceLoadClass;
}
String getJson(CharSequence source) {
if (source == null) {
return null;
}
if (source.toString().endsWith(".json")) {
return getJson(
new ClassPathResource(source.toString(), this.resourceLoadClass));
}
return source.toString();
}
String getJson(String path, Class<?> resourceLoadClass) {
return getJson(new ClassPathResource(path, resourceLoadClass));
}
String getJson(byte[] source) {
return getJson(new ByteArrayInputStream(source));
}
String getJson(File source) {
try {
return getJson(new FileInputStream(source));
}
catch (IOException ex) {
throw new IllegalStateException("Unable to load JSON from " + source, ex);
}
}
String getJson(Resource source) {
try {
return getJson(source.getInputStream());
}
catch (IOException ex) {
throw new IllegalStateException("Unable to load JSON from " + source, ex);
}
}
String getJson(InputStream source) {
try {
return FileCopyUtils
.copyToString(new InputStreamReader(source, this.charset));
}
catch (IOException ex) {
throw new IllegalStateException("Unable to load JSON from InputStream", ex);
}
}
}

View File

@@ -0,0 +1,128 @@
/*
* 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.test.json;
import java.io.IOException;
import java.io.Reader;
import javax.json.bind.Jsonb;
import org.springframework.beans.factory.ObjectFactory;
import org.springframework.core.ResolvableType;
import org.springframework.util.Assert;
/**
* AssertJ based JSON tester backed by Jsonb. Usually instantiated via
* {@link #initFields(Object, Jsonb)}, for example: <pre class="code">
* public class ExampleObjectJsonTests {
*
* private JsonbTester&lt;ExampleObject&gt; json;
*
* &#064;Before
* public void setup() {
* Jsonb jsonb = JsonbBuilder.create();
* JsonbTester.initFields(this, jsonb);
* }
*
* &#064;Test
* public void testWriteJson() throws IOException {
* ExampleObject object = // ...
* assertThat(json.write(object)).isEqualToJson(&quot;expected.json&quot;);
* }
*
* }
* </pre>
*
* See {@link AbstractJsonMarshalTester} for more details.
*
* @param <T> the type under test
* @author Eddú Meléndez
* @since 2.0.0
*/
public class JsonbTester<T> extends AbstractJsonMarshalTester<T> {
private final Jsonb jsonb;
/**
* Create a new uninitialized {@link JsonbTester} instance.
* @param jsonb the Jsonb instance
*/
protected JsonbTester(Jsonb jsonb) {
Assert.notNull(jsonb, "Jsonb must not be null");
this.jsonb = jsonb;
}
/**
* Create a new {@link JsonbTester} instance.
* @param resourceLoadClass the source class used to load resources
* @param type the type under test
* @param jsonb the Jsonb instance
* @see #initFields(Object, Jsonb)
*/
public JsonbTester(Class<?> resourceLoadClass, ResolvableType type, Jsonb jsonb) {
super(resourceLoadClass, type);
Assert.notNull(jsonb, "Jsonb must not be null");
this.jsonb = jsonb;
}
@Override
protected String writeObject(T value, ResolvableType type) throws IOException {
return this.jsonb.toJson(value, type.getType());
}
@Override
protected T readObject(Reader reader, ResolvableType type) throws IOException {
return this.jsonb.fromJson(reader, type.getType());
}
/**
* Utility method to initialize {@link JsonbTester} fields. See {@link JsonbTester
* class-level documentation} for example usage.
* @param testInstance the test instance
* @param jsonb the Jsonb instance
*/
public static void initFields(Object testInstance, Jsonb jsonb) {
new JsonbFieldInitializer().initFields(testInstance, jsonb);
}
/**
* Utility method to initialize {@link JsonbTester} fields. See {@link JsonbTester
* class-level documentation} for example usage.
* @param testInstance the test instance
* @param jsonb an object factory to create the Jsonb instance
*/
public static void initFields(Object testInstance, ObjectFactory<Jsonb> jsonb) {
new JsonbTester.JsonbFieldInitializer().initFields(testInstance, jsonb);
}
/**
* {@link FieldInitializer} for Jsonb.
*/
private static class JsonbFieldInitializer extends FieldInitializer<Jsonb> {
protected JsonbFieldInitializer() {
super(JsonbTester.class);
}
@Override
protected AbstractJsonMarshalTester<Object> createTester(
Class<?> resourceLoadClass, ResolvableType type, Jsonb marshaller) {
return new JsonbTester<>(resourceLoadClass, type, marshaller);
}
}
}

View File

@@ -0,0 +1,69 @@
/*
* 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.test.json;
import org.assertj.core.api.AssertProvider;
import org.springframework.core.ResolvableType;
import org.springframework.util.Assert;
/**
* Object content usually created from {@link AbstractJsonMarshalTester}. Generally used
* only to {@link AssertProvider provide} {@link ObjectContentAssert} to AssertJ
* {@code assertThat} calls.
*
* @param <T> the content type
* @author Phillip Webb
* @since 1.4.0
*/
public final class ObjectContent<T> implements AssertProvider<ObjectContentAssert<T>> {
private final ResolvableType type;
private final T object;
/**
* Create a new {@link ObjectContent} instance.
* @param type the type under test (or {@code null} if not known)
* @param object the actual object content
*/
public ObjectContent(ResolvableType type, T object) {
Assert.notNull(object, "Object must not be null");
this.type = type;
this.object = object;
}
@Override
public ObjectContentAssert<T> assertThat() {
return new ObjectContentAssert<>(this.object);
}
/**
* Return the actual object content.
* @return the object content
*/
public T getObject() {
return this.object;
}
@Override
public String toString() {
return "ObjectContent " + this.object
+ (this.type == null ? "" : " created from " + this.type);
}
}

View File

@@ -0,0 +1,63 @@
/*
* Copyright 2012-2016 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.test.json;
import java.util.Map;
import org.assertj.core.api.AbstractMapAssert;
import org.assertj.core.api.AbstractObjectArrayAssert;
import org.assertj.core.api.AbstractObjectAssert;
import org.assertj.core.api.Assert;
import org.assertj.core.api.Assertions;
import org.assertj.core.internal.Objects;
/**
* AssertJ {@link Assert} for {@link ObjectContent}.
*
* @param <A> The actual type
* @author Phillip Webb
* @since 1.4.0
*/
public class ObjectContentAssert<A>
extends AbstractObjectAssert<ObjectContentAssert<A>, A> {
protected ObjectContentAssert(A actual) {
super(actual, ObjectContentAssert.class);
}
/**
* Verifies that the actual value is an array, and returns an array assertion, to
* allow chaining of array-specific assertions from this call.
* @return an array assertion object
*/
public AbstractObjectArrayAssert<?, Object> asArray() {
Objects.instance().assertIsInstanceOf(this.info, this.actual, Object[].class);
return Assertions.assertThat((Object[]) this.actual);
}
/**
* Verifies that the actual value is a map, and returns a map assertion, to allow
* chaining of map-specific assertions from this call.
* @return a map assertion object
*/
@SuppressWarnings("unchecked")
public AbstractMapAssert<?, ?, Object, Object> asMap() {
Objects.instance().assertIsInstanceOf(this.info, this.actual, Map.class);
return Assertions.assertThat((Map<Object, Object>) this.actual);
}
}

View File

@@ -0,0 +1,20 @@
/*
* Copyright 2012-2016 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.
*/
/**
* Support for testing JSON.
*/
package org.springframework.boot.test.json;

View File

@@ -0,0 +1,108 @@
/*
* 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.test.mock.mockito;
import org.springframework.util.ObjectUtils;
/**
* Base class for {@link MockDefinition} and {@link SpyDefinition}.
*
* @author Phillip Webb
* @see DefinitionsParser
*/
abstract class Definition {
private static final int MULTIPLIER = 31;
private final String name;
private final MockReset reset;
private final boolean proxyTargetAware;
private final QualifierDefinition qualifier;
Definition(String name, MockReset reset, boolean proxyTargetAware,
QualifierDefinition qualifier) {
this.name = name;
this.reset = (reset != null ? reset : MockReset.AFTER);
this.proxyTargetAware = proxyTargetAware;
this.qualifier = qualifier;
}
/**
* Return the name for bean.
* @return the name or {@code null}
*/
public String getName() {
return this.name;
}
/**
* Return the mock reset mode.
* @return the reset mode
*/
public MockReset getReset() {
return this.reset;
}
/**
* Return if AOP advised beans should be proxy target aware.
* @return if proxy target aware
*/
public boolean isProxyTargetAware() {
return this.proxyTargetAware;
}
/**
* Return the qualifier or {@code null}.
* @return the qualifier
*/
public QualifierDefinition getQualifier() {
return this.qualifier;
}
@Override
public int hashCode() {
int result = 1;
result = MULTIPLIER * result + ObjectUtils.nullSafeHashCode(this.name);
result = MULTIPLIER * result + ObjectUtils.nullSafeHashCode(this.reset);
result = MULTIPLIER * result
+ ObjectUtils.nullSafeHashCode(this.proxyTargetAware);
result = MULTIPLIER * result + ObjectUtils.nullSafeHashCode(this.qualifier);
return result;
}
@Override
public boolean equals(Object obj) {
if (obj == this) {
return true;
}
if (obj == null || !getClass().isAssignableFrom(obj.getClass())) {
return false;
}
Definition other = (Definition) obj;
boolean result = true;
result = result && ObjectUtils.nullSafeEquals(this.name, other.name);
result = result && ObjectUtils.nullSafeEquals(this.reset, other.reset);
result = result && ObjectUtils.nullSafeEquals(this.proxyTargetAware,
other.proxyTargetAware);
result = result && ObjectUtils.nullSafeEquals(this.qualifier, other.qualifier);
return result;
}
}

View File

@@ -0,0 +1,139 @@
/*
* 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.test.mock.mockito;
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Field;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;
import org.springframework.core.ResolvableType;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.util.Assert;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.StringUtils;
/**
* Parser to create {@link MockDefinition} and {@link SpyDefinition} instances from
* {@link MockBean @MockBean} and {@link SpyBean @SpyBean} annotations declared on or in a
* class.
*
* @author Phillip Webb
* @author Stephane Nicoll
*/
class DefinitionsParser {
private final Set<Definition> definitions;
private final Map<Definition, Field> definitionFields;
DefinitionsParser() {
this(Collections.<Definition>emptySet());
}
DefinitionsParser(Collection<? extends Definition> existing) {
this.definitions = new LinkedHashSet<>();
this.definitionFields = new LinkedHashMap<>();
if (existing != null) {
this.definitions.addAll(existing);
}
}
public void parse(Class<?> source) {
parseElement(source);
ReflectionUtils.doWithFields(source, this::parseElement);
}
private void parseElement(AnnotatedElement element) {
for (MockBean annotation : AnnotationUtils.getRepeatableAnnotations(element,
MockBean.class, MockBeans.class)) {
parseMockBeanAnnotation(annotation, element);
}
for (SpyBean annotation : AnnotationUtils.getRepeatableAnnotations(element,
SpyBean.class, SpyBeans.class)) {
parseSpyBeanAnnotation(annotation, element);
}
}
private void parseMockBeanAnnotation(MockBean annotation, AnnotatedElement element) {
Set<ResolvableType> typesToMock = getOrDeduceTypes(element, annotation.value());
Assert.state(!typesToMock.isEmpty(),
"Unable to deduce type to mock from " + element);
if (StringUtils.hasLength(annotation.name())) {
Assert.state(typesToMock.size() == 1,
"The name attribute can only be used when mocking a single class");
}
for (ResolvableType typeToMock : typesToMock) {
MockDefinition definition = new MockDefinition(annotation.name(), typeToMock,
annotation.extraInterfaces(), annotation.answer(),
annotation.serializable(), annotation.reset(),
QualifierDefinition.forElement(element));
addDefinition(element, definition, "mock");
}
}
private void parseSpyBeanAnnotation(SpyBean annotation, AnnotatedElement element) {
Set<ResolvableType> typesToSpy = getOrDeduceTypes(element, annotation.value());
Assert.state(!typesToSpy.isEmpty(),
"Unable to deduce type to spy from " + element);
if (StringUtils.hasLength(annotation.name())) {
Assert.state(typesToSpy.size() == 1,
"The name attribute can only be used when spying a single class");
}
for (ResolvableType typeToSpy : typesToSpy) {
SpyDefinition definition = new SpyDefinition(annotation.name(), typeToSpy,
annotation.reset(), annotation.proxyTargetAware(),
QualifierDefinition.forElement(element));
addDefinition(element, definition, "spy");
}
}
private void addDefinition(AnnotatedElement element, Definition definition,
String type) {
boolean isNewDefinition = this.definitions.add(definition);
Assert.state(isNewDefinition, "Duplicate " + type + " definition " + definition);
if (element instanceof Field) {
Field field = (Field) element;
this.definitionFields.put(definition, field);
}
}
private Set<ResolvableType> getOrDeduceTypes(AnnotatedElement element,
Class<?>[] value) {
Set<ResolvableType> types = new LinkedHashSet<>();
for (Class<?> clazz : value) {
types.add(ResolvableType.forClass(clazz));
}
if (types.isEmpty() && element instanceof Field) {
types.add(ResolvableType.forField((Field) element));
}
return types;
}
public Set<Definition> getDefinitions() {
return Collections.unmodifiableSet(this.definitions);
}
public Field getField(Definition definition) {
return this.definitionFields.get(definition);
}
}

View File

@@ -0,0 +1,157 @@
/*
* 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.test.mock.mockito;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Repeatable;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.junit.runner.RunWith;
import org.mockito.Answers;
import org.mockito.MockSettings;
import org.springframework.context.ApplicationContext;
import org.springframework.core.annotation.AliasFor;
import org.springframework.test.context.junit4.SpringRunner;
/**
* Annotation that can be used to add mocks to a Spring {@link ApplicationContext}. Can be
* used as a class level annotation or on fields in either {@code @Configuration} classes,
* or test classes that are {@link RunWith @RunWith} the {@link SpringRunner}.
* <p>
* Mocks can be registered by type or by {@link #name() bean name}. Any existing single
* bean of the same type defined in the context will be replaced by the mock. If no
* existing bean is defined a new one will be added. Dependencies that are known to the
* application context but are not beans (such as those
* {@link org.springframework.beans.factory.config.ConfigurableListableBeanFactory#registerResolvableDependency(Class, Object)
* registered directly} ) will not be found and a mocked bean will be added to the context
* alongside the existing dependency.
* <p>
* When {@code @MockBean} is used on a field, as well as being registered in the
* application context, the mock will also be injected into the field. Typical usage might
* be: <pre class="code">
* &#064;RunWith(SpringRunner.class)
* public class ExampleTests {
*
* &#064;MockBean
* private ExampleService service;
*
* &#064;Autowired
* private UserOfService userOfService;
*
* &#064;Test
* public void testUserOfService() {
* given(this.service.greet()).willReturn("Hello");
* String actual = this.userOfService.makeUse();
* assertEquals("Was: Hello", actual);
* }
*
* &#064;Configuration
* &#064;Import(UserOfService.class) // A &#064;Component injected with ExampleService
* static class Config {
* }
*
*
* }
* </pre> If there is more than one bean of the requested type, qualifier metadata must be
* specified at field level: <pre class="code">
* &#064;RunWith(SpringRunner.class)
* public class ExampleTests {
*
* &#064;MockBean
* &#064;Qualifier("example")
* private ExampleService service;
*
* ...
* }
* </pre>
* <p>
* This annotation is {@code @Repeatable} and may be specified multiple times when working
* with Java 8 or contained within an {@link MockBeans @MockBeans} annotation.
*
* @author Phillip Webb
* @since 1.4.0
* @see MockitoPostProcessor
*/
@Target({ ElementType.TYPE, ElementType.FIELD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Repeatable(MockBeans.class)
public @interface MockBean {
/**
* The name of the bean to register or replace. If not specified the name will either
* be generated or, if the mock replaces an existing bean, the existing name will be
* used.
* @return the name of the bean
*/
String name() default "";
/**
* The classes to mock. This is an alias of {@link #classes()} which can be used for
* brevity if no other attributes are defined. See {@link #classes()} for details.
* @return the classes to mock
*/
@AliasFor("classes")
Class<?>[] value() default {};
/**
* The classes to mock. Each class specified here will result in a mock being created
* and registered with the application context. Classes can be omitted when the
* annotation is used on a field.
* <p>
* When {@code @MockBean} also defines a {@code name} this attribute can only contain
* a single value.
* <p>
* If this is the only specified attribute consider using the {@code value} alias
* instead.
* @return the classes to mock
*/
@AliasFor("value")
Class<?>[] classes() default {};
/**
* Any extra interfaces that should also be declared on the mock. See
* {@link MockSettings#extraInterfaces(Class...)} for details.
* @return any extra interfaces
*/
Class<?>[] extraInterfaces() default {};
/**
* The {@link Answers} type to use on the mock.
* @return the answer type
*/
Answers answer() default Answers.RETURNS_DEFAULTS;
/**
* If the generated mock is serializable. See {@link MockSettings#serializable()} for
* details.
* @return if the mock is serializable
*/
boolean serializable() default false;
/**
* The reset mode to apply to the mock bean. The default is {@link MockReset#AFTER}
* meaning that mocks are automatically reset after each test method is invoked.
* @return the reset mode
*/
MockReset reset() default MockReset.AFTER;
}

View File

@@ -0,0 +1,47 @@
/*
* Copyright 2012-2016 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.test.mock.mockito;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Container annotation that aggregates several {@link MockBean} annotations.
* <p>
* Can be used natively, declaring several nested {@link MockBean} annotations. Can also
* be used in conjunction with Java 8's support for <em>repeatable annotations</em>, where
* {@link MockBean} can simply be declared several times on the same
* {@linkplain ElementType#TYPE type}, implicitly generating this container annotation.
*
* @author Phillip Webb
* @since 1.4.0
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
public @interface MockBeans {
/**
* Return the contained {@link MockBean} annotations.
* @return the mock beans
*/
MockBean[] value();
}

View File

@@ -0,0 +1,159 @@
/*
* 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.test.mock.mockito;
import java.util.Arrays;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.Set;
import org.mockito.Answers;
import org.mockito.MockSettings;
import org.mockito.Mockito;
import org.springframework.core.ResolvableType;
import org.springframework.core.style.ToStringCreator;
import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;
/**
* A complete definition that can be used to create a Mockito mock.
*
* @author Phillip Webb
*/
class MockDefinition extends Definition {
private static final int MULTIPLIER = 31;
private final ResolvableType typeToMock;
private final Set<Class<?>> extraInterfaces;
private final Answers answer;
private final boolean serializable;
MockDefinition(String name, ResolvableType typeToMock, Class<?>[] extraInterfaces,
Answers answer, boolean serializable, MockReset reset,
QualifierDefinition qualifier) {
super(name, reset, false, qualifier);
Assert.notNull(typeToMock, "TypeToMock must not be null");
this.typeToMock = typeToMock;
this.extraInterfaces = asClassSet(extraInterfaces);
this.answer = (answer != null ? answer : Answers.RETURNS_DEFAULTS);
this.serializable = serializable;
}
private Set<Class<?>> asClassSet(Class<?>[] classes) {
Set<Class<?>> classSet = new LinkedHashSet<>();
if (classes != null) {
classSet.addAll(Arrays.asList(classes));
}
return Collections.unmodifiableSet(classSet);
}
/**
* Return the type that should be mocked.
* @return the type to mock; never {@code null}
*/
public ResolvableType getTypeToMock() {
return this.typeToMock;
}
/**
* Return the extra interfaces.
* @return the extra interfaces or an empty set
*/
public Set<Class<?>> getExtraInterfaces() {
return this.extraInterfaces;
}
/**
* Return the answers mode.
* @return the answers mode; never {@code null}
*/
public Answers getAnswer() {
return this.answer;
}
/**
* Return if the mock is serializable.
* @return if the mock is serializable
*/
public boolean isSerializable() {
return this.serializable;
}
@Override
public int hashCode() {
int result = super.hashCode();
result = MULTIPLIER * result + ObjectUtils.nullSafeHashCode(this.typeToMock);
result = MULTIPLIER * result + ObjectUtils.nullSafeHashCode(this.extraInterfaces);
result = MULTIPLIER * result + ObjectUtils.nullSafeHashCode(this.answer);
result = MULTIPLIER * result + Boolean.hashCode(this.serializable);
return result;
}
@Override
public boolean equals(Object obj) {
if (obj == this) {
return true;
}
if (obj == null || obj.getClass() != getClass()) {
return false;
}
MockDefinition other = (MockDefinition) obj;
boolean result = super.equals(obj);
result = result && ObjectUtils.nullSafeEquals(this.typeToMock, other.typeToMock);
result = result && ObjectUtils.nullSafeEquals(this.extraInterfaces,
other.extraInterfaces);
result = result && ObjectUtils.nullSafeEquals(this.answer, other.answer);
result = result && this.serializable == other.serializable;
return result;
}
@Override
public String toString() {
return new ToStringCreator(this).append("name", getName())
.append("typeToMock", this.typeToMock)
.append("extraInterfaces", this.extraInterfaces)
.append("answer", this.answer).append("serializable", this.serializable)
.append("reset", getReset()).toString();
}
public <T> T createMock() {
return createMock(getName());
}
@SuppressWarnings("unchecked")
public <T> T createMock(String name) {
MockSettings settings = MockReset.withSettings(getReset());
if (StringUtils.hasLength(name)) {
settings.name(name);
}
if (!this.extraInterfaces.isEmpty()) {
settings.extraInterfaces(this.extraInterfaces.toArray(new Class<?>[] {}));
}
settings.defaultAnswer(this.answer);
if (this.serializable) {
settings.serializable();
}
return (T) Mockito.mock(this.typeToMock.resolve(), settings);
}
}

View File

@@ -0,0 +1,139 @@
/*
* 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.test.mock.mockito;
import java.util.List;
import org.mockito.MockSettings;
import org.mockito.MockingDetails;
import org.mockito.Mockito;
import org.mockito.listeners.InvocationListener;
import org.mockito.listeners.MethodInvocationReport;
import org.mockito.mock.MockCreationSettings;
import org.springframework.util.Assert;
/**
* Reset strategy used on a mock bean. Usually applied to a mock via the
* {@link MockBean @MockBean} annotation but can also be directly applied to any mock in
* the {@code ApplicationContext} using the static methods.
*
* @author Phillip Webb
* @since 1.4.0
* @see ResetMocksTestExecutionListener
*/
public enum MockReset {
/**
* Reset the mock before the test method runs.
*/
BEFORE,
/**
* Reset the mock after the test method runs.
*/
AFTER,
/**
* Don't reset the mock.
*/
NONE;
/**
* Create {@link MockSettings settings} to be used with mocks where reset should occur
* before each test method runs.
* @return mock settings
*/
public static MockSettings before() {
return withSettings(BEFORE);
}
/**
* Create {@link MockSettings settings} to be used with mocks where reset should occur
* after each test method runs.
* @return mock settings
*/
public static MockSettings after() {
return withSettings(AFTER);
}
/**
* Create {@link MockSettings settings} to be used with mocks where a specific reset
* should occur.
* @param reset the reset type
* @return mock settings
*/
public static MockSettings withSettings(MockReset reset) {
return apply(reset, Mockito.withSettings());
}
/**
* Apply {@link MockReset} to existing {@link MockSettings settings}.
* @param reset the reset type
* @param settings the settings
* @return the configured settings
*/
public static MockSettings apply(MockReset reset, MockSettings settings) {
Assert.notNull(settings, "Settings must not be null");
if (reset != null && reset != NONE) {
settings.invocationListeners(new ResetInvocationListener(reset));
}
return settings;
}
/**
* Get the {@link MockReset} associated with the given mock.
* @param mock the source mock
* @return the reset type (never {@code null})
*/
static MockReset get(Object mock) {
MockReset reset = MockReset.NONE;
MockingDetails mockingDetails = Mockito.mockingDetails(mock);
if (mockingDetails.isMock()) {
MockCreationSettings<?> settings = mockingDetails.getMockCreationSettings();
List<InvocationListener> listeners = settings.getInvocationListeners();
for (Object listener : listeners) {
if (listener instanceof ResetInvocationListener) {
reset = ((ResetInvocationListener) listener).getReset();
}
}
}
return reset;
}
/**
* Dummy {@link InvocationListener} used to hold the {@link MockReset} value.
*/
private static class ResetInvocationListener implements InvocationListener {
private final MockReset reset;
ResetInvocationListener(MockReset reset) {
this.reset = reset;
}
public MockReset getReset() {
return this.reset;
}
@Override
public void reportInvocation(MethodInvocationReport methodInvocationReport) {
}
}
}

View File

@@ -0,0 +1,133 @@
/*
* 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.test.mock.mockito;
import java.util.Collections;
import java.util.List;
import org.aopalliance.aop.Advice;
import org.aopalliance.intercept.Interceptor;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.mockito.internal.matchers.LocalizedMatcher;
import org.mockito.internal.progress.ArgumentMatcherStorage;
import org.mockito.internal.progress.MockingProgress;
import org.mockito.internal.progress.ThreadSafeMockingProgress;
import org.mockito.internal.verification.MockAwareVerificationMode;
import org.mockito.verification.VerificationMode;
import org.springframework.aop.Advisor;
import org.springframework.aop.framework.Advised;
import org.springframework.aop.support.AopUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.util.AopTestUtils;
import org.springframework.util.Assert;
/**
* AOP {@link Interceptor} that attempts to make AOP proxy beans work with Mockito. Works
* by bypassing AOP advice when a method is invoked via
* {@code Mockito#verify(Object) verify(mock)}.
*
* @author Phillip Webb
*/
class MockitoAopProxyTargetInterceptor implements MethodInterceptor {
private final Object source;
private final Object target;
private final Verification verification = new Verification();
MockitoAopProxyTargetInterceptor(Object source, Object target) throws Exception {
this.source = source;
this.target = target;
}
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
if (this.verification.isVerifying()) {
this.verification.replaceVerifyMock(this.source, this.target);
return AopUtils.invokeJoinpointUsingReflection(this.target,
invocation.getMethod(), invocation.getArguments());
}
return invocation.proceed();
}
@Autowired
public static void applyTo(Object source) {
Assert.state(AopUtils.isAopProxy(source), "Source must be an AOP proxy");
try {
Advised advised = (Advised) source;
for (Advisor advisor : advised.getAdvisors()) {
if (advisor instanceof MockitoAopProxyTargetInterceptor) {
return;
}
}
Object target = AopTestUtils.getUltimateTargetObject(source);
Advice advice = new MockitoAopProxyTargetInterceptor(source, target);
advised.addAdvice(0, advice);
}
catch (Exception ex) {
throw new IllegalStateException("Unable to apply Mockito AOP support", ex);
}
}
private static class Verification {
private final Object monitor = new Object();
private final MockingProgress progress = ThreadSafeMockingProgress
.mockingProgress();
public boolean isVerifying() {
synchronized (this.monitor) {
VerificationMode mode = this.progress.pullVerificationMode();
if (mode != null) {
resetVerificationStarted(mode);
return true;
}
return false;
}
}
public void replaceVerifyMock(Object source, Object target) {
synchronized (this.monitor) {
VerificationMode mode = this.progress.pullVerificationMode();
if (mode != null) {
if (mode instanceof MockAwareVerificationMode) {
MockAwareVerificationMode mockAwareMode = (MockAwareVerificationMode) mode;
if (mockAwareMode.getMock() == source) {
mode = new MockAwareVerificationMode(target, mode,
Collections.emptySet());
}
}
resetVerificationStarted(mode);
}
}
}
private void resetVerificationStarted(VerificationMode mode) {
ArgumentMatcherStorage storage = this.progress.getArgumentMatcherStorage();
List<LocalizedMatcher> matchers = storage.pullLocalizedMatchers();
this.progress.verificationStarted(mode);
matchers.stream().map(LocalizedMatcher::getMatcher)
.forEach(storage::reportMatcher);
}
}
}

View File

@@ -0,0 +1,41 @@
/*
* 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.test.mock.mockito;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
/**
* Beans created using Mockito.
*
* @author Andy Wilkinson
*/
class MockitoBeans implements Iterable<Object> {
private final List<Object> beans = new ArrayList<>();
void add(Object bean) {
this.beans.add(bean);
}
@Override
public Iterator<Object> iterator() {
return this.beans.iterator();
}
}

View File

@@ -0,0 +1,66 @@
/*
* 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.test.mock.mockito;
import java.util.LinkedHashSet;
import java.util.Set;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.test.context.ContextCustomizer;
import org.springframework.test.context.MergedContextConfiguration;
/**
* A {@link ContextCustomizer} to add Mockito support.
*
* @author Phillip Webb
*/
class MockitoContextCustomizer implements ContextCustomizer {
private final Set<Definition> definitions;
MockitoContextCustomizer(Set<? extends Definition> definitions) {
this.definitions = new LinkedHashSet<>(definitions);
}
@Override
public void customizeContext(ConfigurableApplicationContext context,
MergedContextConfiguration mergedContextConfiguration) {
if (context instanceof BeanDefinitionRegistry) {
MockitoPostProcessor.register((BeanDefinitionRegistry) context,
this.definitions);
}
}
@Override
public int hashCode() {
return this.definitions.hashCode();
}
@Override
public boolean equals(Object obj) {
if (obj == this) {
return true;
}
if (obj == null || obj.getClass() != getClass()) {
return false;
}
MockitoContextCustomizer other = (MockitoContextCustomizer) obj;
return this.definitions.equals(other.definitions);
}
}

View File

@@ -0,0 +1,42 @@
/*
* Copyright 2012-2016 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.test.mock.mockito;
import java.util.List;
import org.springframework.test.context.ContextConfigurationAttributes;
import org.springframework.test.context.ContextCustomizer;
import org.springframework.test.context.ContextCustomizerFactory;
/**
* A {@link ContextCustomizerFactory} to add Mockito support.
*
* @author Phillip Webb
*/
class MockitoContextCustomizerFactory implements ContextCustomizerFactory {
@Override
public ContextCustomizer createContextCustomizer(Class<?> testClass,
List<ContextConfigurationAttributes> configAttributes) {
// We gather the explicit mock definitions here since they form part of the
// MergedContextConfiguration key. Different mocks need to have a different key.
DefinitionsParser parser = new DefinitionsParser();
parser.parse(testClass);
return new MockitoContextCustomizer(parser.getDefinitions());
}
}

View File

@@ -0,0 +1,552 @@
/*
* 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.test.mock.mockito;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Field;
import java.util.Arrays;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
import org.springframework.aop.scope.ScopedProxyUtils;
import org.springframework.aop.support.AopUtils;
import org.springframework.beans.BeansException;
import org.springframework.beans.PropertyValues;
import org.springframework.beans.factory.BeanClassLoaderAware;
import org.springframework.beans.factory.BeanCreationException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.BeanFactoryUtils;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.NoUniqueBeanDefinitionException;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.config.ConstructorArgumentValues;
import org.springframework.beans.factory.config.ConstructorArgumentValues.ValueHolder;
import org.springframework.beans.factory.config.InstantiationAwareBeanPostProcessorAdapter;
import org.springframework.beans.factory.config.RuntimeBeanReference;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.BeanNameGenerator;
import org.springframework.beans.factory.support.DefaultBeanNameGenerator;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.ConfigurationClassPostProcessor;
import org.springframework.core.Conventions;
import org.springframework.core.Ordered;
import org.springframework.core.PriorityOrdered;
import org.springframework.core.ResolvableType;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.ObjectUtils;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.StringUtils;
/**
* A {@link BeanFactoryPostProcessor} used to register and inject
* {@link MockBean @MockBeans} with the {@link ApplicationContext}. An initial set of
* definitions can be passed to the processor with additional definitions being
* automatically created from {@code @Configuration} classes that use
* {@link MockBean @MockBean}.
*
* @author Phillip Webb
* @author Andy Wilkinson
* @author Stephane Nicoll
* @since 1.4.0
*/
public class MockitoPostProcessor extends InstantiationAwareBeanPostProcessorAdapter
implements BeanClassLoaderAware, BeanFactoryAware, BeanFactoryPostProcessor,
Ordered {
private static final String FACTORY_BEAN_OBJECT_TYPE = "factoryBeanObjectType";
private static final String BEAN_NAME = MockitoPostProcessor.class.getName();
private static final String CONFIGURATION_CLASS_ATTRIBUTE = Conventions
.getQualifiedAttributeName(ConfigurationClassPostProcessor.class,
"configurationClass");
private final Set<Definition> definitions;
private ClassLoader classLoader;
private BeanFactory beanFactory;
private final BeanNameGenerator beanNameGenerator = new DefaultBeanNameGenerator();
private final MockitoBeans mockitoBeans = new MockitoBeans();
private Map<Definition, String> beanNameRegistry = new HashMap<>();
private Map<Field, RegisteredField> fieldRegistry = new HashMap<>();
private Map<String, SpyDefinition> spies = new HashMap<>();
/**
* Create a new {@link MockitoPostProcessor} instance with the given initial
* definitions.
* @param definitions the initial definitions
*/
public MockitoPostProcessor(Set<Definition> definitions) {
this.definitions = definitions;
}
@Override
public void setBeanClassLoader(ClassLoader classLoader) {
this.classLoader = classLoader;
}
@Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
Assert.isInstanceOf(ConfigurableListableBeanFactory.class, beanFactory,
"Mock beans can only be used with a ConfigurableListableBeanFactory");
this.beanFactory = beanFactory;
}
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory)
throws BeansException {
Assert.isInstanceOf(BeanDefinitionRegistry.class, beanFactory,
"@MockBean can only be used on bean factories that "
+ "implement BeanDefinitionRegistry");
postProcessBeanFactory(beanFactory, (BeanDefinitionRegistry) beanFactory);
}
private void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory,
BeanDefinitionRegistry registry) {
beanFactory.registerSingleton(MockitoBeans.class.getName(), this.mockitoBeans);
DefinitionsParser parser = new DefinitionsParser(this.definitions);
for (Class<?> configurationClass : getConfigurationClasses(beanFactory)) {
parser.parse(configurationClass);
}
Set<Definition> definitions = parser.getDefinitions();
for (Definition definition : definitions) {
Field field = parser.getField(definition);
register(beanFactory, registry, definition, field);
}
}
private Set<Class<?>> getConfigurationClasses(
ConfigurableListableBeanFactory beanFactory) {
Set<Class<?>> configurationClasses = new LinkedHashSet<>();
for (BeanDefinition beanDefinition : getConfigurationBeanDefinitions(beanFactory)
.values()) {
configurationClasses.add(ClassUtils.resolveClassName(
beanDefinition.getBeanClassName(), this.classLoader));
}
return configurationClasses;
}
private Map<String, BeanDefinition> getConfigurationBeanDefinitions(
ConfigurableListableBeanFactory beanFactory) {
Map<String, BeanDefinition> definitions = new LinkedHashMap<>();
for (String beanName : beanFactory.getBeanDefinitionNames()) {
BeanDefinition definition = beanFactory.getBeanDefinition(beanName);
if (definition.getAttribute(CONFIGURATION_CLASS_ATTRIBUTE) != null) {
definitions.put(beanName, definition);
}
}
return definitions;
}
private void register(ConfigurableListableBeanFactory beanFactory,
BeanDefinitionRegistry registry, Definition definition, Field field) {
if (definition instanceof MockDefinition) {
registerMock(beanFactory, registry, (MockDefinition) definition, field);
}
else if (definition instanceof SpyDefinition) {
registerSpy(beanFactory, registry, (SpyDefinition) definition, field);
}
}
private void registerMock(ConfigurableListableBeanFactory beanFactory,
BeanDefinitionRegistry registry, MockDefinition definition, Field field) {
RootBeanDefinition beanDefinition = createBeanDefinition(definition);
String beanName = getBeanName(beanFactory, registry, definition, beanDefinition);
String transformedBeanName = BeanFactoryUtils.transformedBeanName(beanName);
beanDefinition.getConstructorArgumentValues().addIndexedArgumentValue(1,
beanName);
if (registry.containsBeanDefinition(transformedBeanName)) {
registry.removeBeanDefinition(transformedBeanName);
}
registry.registerBeanDefinition(transformedBeanName, beanDefinition);
Object mock = createMock(definition, beanName);
beanFactory.registerSingleton(transformedBeanName, mock);
this.mockitoBeans.add(mock);
this.beanNameRegistry.put(definition, beanName);
if (field != null) {
this.fieldRegistry.put(field, new RegisteredField(definition, beanName));
}
}
private RootBeanDefinition createBeanDefinition(MockDefinition mockDefinition) {
RootBeanDefinition definition = new RootBeanDefinition(
mockDefinition.getTypeToMock().resolve());
definition.setTargetType(mockDefinition.getTypeToMock());
definition.setFactoryBeanName(BEAN_NAME);
definition.setFactoryMethodName("createMock");
definition.getConstructorArgumentValues().addIndexedArgumentValue(0,
mockDefinition);
if (mockDefinition.getQualifier() != null) {
mockDefinition.getQualifier().applyTo(definition);
}
return definition;
}
/**
* Factory method used by defined beans to actually create the mock.
* @param mockDefinition the mock definition
* @param name the bean name
* @return the mock instance
*/
protected final Object createMock(MockDefinition mockDefinition, String name) {
return mockDefinition.createMock(name + " bean");
}
private String getBeanName(ConfigurableListableBeanFactory beanFactory,
BeanDefinitionRegistry registry, MockDefinition mockDefinition,
RootBeanDefinition beanDefinition) {
if (StringUtils.hasLength(mockDefinition.getName())) {
return mockDefinition.getName();
}
Set<String> existingBeans = findCandidateBeans(beanFactory, mockDefinition);
if (existingBeans.isEmpty()) {
return this.beanNameGenerator.generateBeanName(beanDefinition, registry);
}
if (existingBeans.size() == 1) {
return existingBeans.iterator().next();
}
throw new IllegalStateException(
"Unable to register mock bean " + mockDefinition.getTypeToMock()
+ " expected a single matching bean to replace but found "
+ existingBeans);
}
private void registerSpy(ConfigurableListableBeanFactory beanFactory,
BeanDefinitionRegistry registry, SpyDefinition definition, Field field) {
String[] existingBeans = getExistingBeans(beanFactory, definition.getTypeToSpy());
if (ObjectUtils.isEmpty(existingBeans)) {
createSpy(registry, definition, field);
}
else {
registerSpies(registry, definition, field, existingBeans);
}
}
private Set<String> findCandidateBeans(ConfigurableListableBeanFactory beanFactory,
MockDefinition mockDefinition) {
QualifierDefinition qualifier = mockDefinition.getQualifier();
Set<String> candidates = new TreeSet<>();
for (String candidate : getExistingBeans(beanFactory,
mockDefinition.getTypeToMock())) {
if (qualifier == null || qualifier.matches(beanFactory, candidate)) {
candidates.add(candidate);
}
}
return candidates;
}
private String[] getExistingBeans(ConfigurableListableBeanFactory beanFactory,
ResolvableType type) {
Set<String> beans = new LinkedHashSet<>(
Arrays.asList(beanFactory.getBeanNamesForType(type)));
String resolvedTypeName = type.resolve(Object.class).getName();
for (String beanName : beanFactory.getBeanNamesForType(FactoryBean.class)) {
beanName = BeanFactoryUtils.transformedBeanName(beanName);
BeanDefinition beanDefinition = beanFactory.getBeanDefinition(beanName);
if (resolvedTypeName
.equals(beanDefinition.getAttribute(FACTORY_BEAN_OBJECT_TYPE))) {
beans.add(beanName);
}
}
beans.removeIf(this::isScopedTarget);
return beans.toArray(new String[beans.size()]);
}
private boolean isScopedTarget(String beanName) {
try {
return ScopedProxyUtils.isScopedTarget(beanName);
}
catch (Throwable ex) {
return false;
}
}
private void createSpy(BeanDefinitionRegistry registry, SpyDefinition definition,
Field field) {
RootBeanDefinition beanDefinition = new RootBeanDefinition(
definition.getTypeToSpy().resolve());
String beanName = this.beanNameGenerator.generateBeanName(beanDefinition,
registry);
registry.registerBeanDefinition(beanName, beanDefinition);
registerSpy(definition, field, beanName);
}
private void registerSpies(BeanDefinitionRegistry registry, SpyDefinition definition,
Field field, String[] existingBeans) {
try {
registerSpy(definition, field,
determineBeanName(existingBeans, definition, registry));
}
catch (RuntimeException ex) {
throw new IllegalStateException(
"Unable to register spy bean " + definition.getTypeToSpy(), ex);
}
}
private String determineBeanName(String[] existingBeans, SpyDefinition definition,
BeanDefinitionRegistry registry) {
if (StringUtils.hasText(definition.getName())) {
return definition.getName();
}
if (existingBeans.length == 1) {
return existingBeans[0];
}
return determinePrimaryCandidate(registry, existingBeans,
definition.getTypeToSpy());
}
private String determinePrimaryCandidate(BeanDefinitionRegistry registry,
String[] candidateBeanNames, ResolvableType type) {
String primaryBeanName = null;
for (String candidateBeanName : candidateBeanNames) {
BeanDefinition beanDefinition = registry.getBeanDefinition(candidateBeanName);
if (beanDefinition.isPrimary()) {
if (primaryBeanName != null) {
throw new NoUniqueBeanDefinitionException(type.resolve(),
candidateBeanNames.length,
"more than one 'primary' bean found among candidates: "
+ Arrays.asList(candidateBeanNames));
}
primaryBeanName = candidateBeanName;
}
}
return primaryBeanName;
}
private void registerSpy(SpyDefinition definition, Field field, String beanName) {
this.spies.put(beanName, definition);
this.beanNameRegistry.put(definition, beanName);
if (field != null) {
this.fieldRegistry.put(field, new RegisteredField(definition, beanName));
}
}
protected Object createSpyIfNecessary(Object bean, String beanName)
throws BeansException {
SpyDefinition definition = this.spies.get(beanName);
if (definition != null) {
bean = definition.createSpy(beanName, bean);
}
return bean;
}
@Override
public PropertyValues postProcessPropertyValues(PropertyValues pvs,
PropertyDescriptor[] pds, final Object bean, String beanName)
throws BeansException {
ReflectionUtils.doWithFields(bean.getClass(),
(field) -> postProcessField(bean, field));
return pvs;
}
private void postProcessField(Object bean, Field field) {
RegisteredField registered = this.fieldRegistry.get(field);
if (registered != null && StringUtils.hasLength(registered.getBeanName())) {
inject(field, bean, registered.getBeanName(), registered.getDefinition());
}
}
void inject(Field field, Object target, Definition definition) {
String beanName = this.beanNameRegistry.get(definition);
Assert.state(StringUtils.hasLength(beanName),
"No bean found for definition " + definition);
inject(field, target, beanName, definition);
}
private void inject(Field field, Object target, String beanName,
Definition definition) {
try {
field.setAccessible(true);
Assert.state(ReflectionUtils.getField(field, target) == null,
"The field " + field + " cannot have an existing value");
Object bean = this.beanFactory.getBean(beanName, field.getType());
if (definition.isProxyTargetAware() && isAopProxy(bean)) {
MockitoAopProxyTargetInterceptor.applyTo(bean);
}
ReflectionUtils.setField(field, target, bean);
}
catch (Throwable ex) {
throw new BeanCreationException("Could not inject field: " + field, ex);
}
}
private boolean isAopProxy(Object object) {
try {
return AopUtils.isAopProxy(object);
}
catch (Throwable ex) {
return false;
}
}
@Override
public int getOrder() {
return Ordered.LOWEST_PRECEDENCE - 10;
}
/**
* Register the processor with a {@link BeanDefinitionRegistry}. Not required when
* using the {@link SpringRunner} as registration is automatic.
* @param registry the bean definition registry
*/
public static void register(BeanDefinitionRegistry registry) {
register(registry, null);
}
/**
* Register the processor with a {@link BeanDefinitionRegistry}. Not required when
* using the {@link SpringRunner} as registration is automatic.
* @param registry the bean definition registry
* @param definitions the initial mock/spy definitions
*/
public static void register(BeanDefinitionRegistry registry,
Set<Definition> definitions) {
register(registry, MockitoPostProcessor.class, definitions);
}
/**
* Register the processor with a {@link BeanDefinitionRegistry}. Not required when
* using the {@link SpringRunner} as registration is automatic.
* @param registry the bean definition registry
* @param postProcessor the post processor class to register
* @param definitions the initial mock/spy definitions
*/
@SuppressWarnings("unchecked")
public static void register(BeanDefinitionRegistry registry,
Class<? extends MockitoPostProcessor> postProcessor,
Set<Definition> definitions) {
SpyPostProcessor.register(registry);
BeanDefinition definition = getOrAddBeanDefinition(registry, postProcessor);
ValueHolder constructorArg = definition.getConstructorArgumentValues()
.getIndexedArgumentValue(0, Set.class);
Set<Definition> existing = (Set<Definition>) constructorArg.getValue();
if (definitions != null) {
existing.addAll(definitions);
}
}
private static BeanDefinition getOrAddBeanDefinition(BeanDefinitionRegistry registry,
Class<? extends MockitoPostProcessor> postProcessor) {
if (!registry.containsBeanDefinition(BEAN_NAME)) {
RootBeanDefinition definition = new RootBeanDefinition(postProcessor);
definition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
ConstructorArgumentValues constructorArguments = definition
.getConstructorArgumentValues();
constructorArguments.addIndexedArgumentValue(0,
new LinkedHashSet<MockDefinition>());
registry.registerBeanDefinition(BEAN_NAME, definition);
return definition;
}
return registry.getBeanDefinition(BEAN_NAME);
}
/**
* {@link BeanPostProcessor} to handle {@link SpyBean} definitions. Registered as a
* separate processor so that it can be ordered above AOP post processors.
*/
static class SpyPostProcessor extends InstantiationAwareBeanPostProcessorAdapter
implements PriorityOrdered {
private static final String BEAN_NAME = SpyPostProcessor.class.getName();
private final MockitoPostProcessor mockitoPostProcessor;
SpyPostProcessor(MockitoPostProcessor mockitoPostProcessor) {
this.mockitoPostProcessor = mockitoPostProcessor;
}
@Override
public int getOrder() {
return Ordered.HIGHEST_PRECEDENCE;
}
@Override
public Object getEarlyBeanReference(Object bean, String beanName)
throws BeansException {
return createSpyIfNecessary(bean, beanName);
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName)
throws BeansException {
if (bean instanceof FactoryBean) {
return bean;
}
return createSpyIfNecessary(bean, beanName);
}
private Object createSpyIfNecessary(Object bean, String beanName) {
return this.mockitoPostProcessor.createSpyIfNecessary(bean, beanName);
}
public static void register(BeanDefinitionRegistry registry) {
if (!registry.containsBeanDefinition(BEAN_NAME)) {
RootBeanDefinition definition = new RootBeanDefinition(
SpyPostProcessor.class);
definition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
ConstructorArgumentValues constructorArguments = definition
.getConstructorArgumentValues();
constructorArguments.addIndexedArgumentValue(0,
new RuntimeBeanReference(MockitoPostProcessor.BEAN_NAME));
registry.registerBeanDefinition(BEAN_NAME, definition);
}
}
}
/**
* A registered field item.
*/
private static class RegisteredField {
private final Definition definition;
private final String beanName;
RegisteredField(Definition definition, String beanName) {
this.definition = definition;
this.beanName = beanName;
}
public Definition getDefinition() {
return this.definition;
}
public String getBeanName() {
return this.beanName;
}
}
}

View File

@@ -0,0 +1,101 @@
/*
* 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.test.mock.mockito;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.util.LinkedHashSet;
import java.util.Set;
import org.mockito.Captor;
import org.mockito.MockitoAnnotations;
import org.springframework.context.ApplicationContext;
import org.springframework.test.context.TestContext;
import org.springframework.test.context.TestExecutionListener;
import org.springframework.test.context.support.AbstractTestExecutionListener;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.ReflectionUtils.FieldCallback;
/**
* {@link TestExecutionListener} to trigger {@link MockitoAnnotations#initMocks(Object)}
* when {@link MockBean @MockBean} annotations are used. Primarily to allow {@link Captor}
* annotations.
*
* @author Phillip Webb
* @since 1.4.2
*/
public class MockitoTestExecutionListener extends AbstractTestExecutionListener {
@Override
public void prepareTestInstance(TestContext testContext) throws Exception {
if (hasMockitoAnnotations(testContext)) {
MockitoAnnotations.initMocks(testContext.getTestInstance());
}
injectFields(testContext);
}
private boolean hasMockitoAnnotations(TestContext testContext) {
MockitoAnnotationCollection collector = new MockitoAnnotationCollection();
ReflectionUtils.doWithFields(testContext.getTestClass(), collector);
return collector.hasAnnotations();
}
private void injectFields(TestContext testContext) {
DefinitionsParser parser = new DefinitionsParser();
parser.parse(testContext.getTestClass());
if (!parser.getDefinitions().isEmpty()) {
injectFields(testContext, parser);
}
}
private void injectFields(TestContext testContext, DefinitionsParser parser) {
ApplicationContext applicationContext = testContext.getApplicationContext();
MockitoPostProcessor postProcessor = applicationContext
.getBean(MockitoPostProcessor.class);
for (Definition definition : parser.getDefinitions()) {
Field field = parser.getField(definition);
if (field != null) {
postProcessor.inject(field, testContext.getTestInstance(), definition);
}
}
}
/**
* {@link FieldCallback} to collect Mockito annotations.
*/
private static class MockitoAnnotationCollection implements FieldCallback {
private final Set<Annotation> annotations = new LinkedHashSet<>();
@Override
public void doWith(Field field)
throws IllegalArgumentException, IllegalAccessException {
for (Annotation annotation : field.getDeclaredAnnotations()) {
if (annotation.annotationType().getName().startsWith("org.mockito")) {
this.annotations.add(annotation);
}
}
}
public boolean hasAnnotations() {
return !this.annotations.isEmpty();
}
}
}

View File

@@ -0,0 +1,110 @@
/*
* 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.test.mock.mockito;
import java.lang.annotation.Annotation;
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Field;
import java.util.HashSet;
import java.util.Set;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.config.DependencyDescriptor;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.core.annotation.AnnotationUtils;
/**
* Definition of a Spring {@link Qualifier @Qualifier}.
*
* @author Phillip Webb
* @author Stephane Nicoll
* @see Definition
*/
class QualifierDefinition {
private final Field field;
private final DependencyDescriptor descriptor;
private final Set<Annotation> annotations;
QualifierDefinition(Field field, Set<Annotation> annotations) {
// We can't use the field or descriptor as part of the context key
// but we can assume that if two fields have the same qualifiers then
// it's safe for Spring to use either for qualifier logic
this.field = field;
this.descriptor = new DependencyDescriptor(field, true);
this.annotations = annotations;
}
public boolean matches(ConfigurableListableBeanFactory beanFactory, String beanName) {
return beanFactory.isAutowireCandidate(beanName, this.descriptor);
}
public void applyTo(RootBeanDefinition definition) {
definition.setQualifiedElement(this.field);
}
@Override
public int hashCode() {
return this.annotations.hashCode();
}
@Override
public boolean equals(Object obj) {
if (obj == this) {
return true;
}
if (obj == null || !getClass().isAssignableFrom(obj.getClass())) {
return false;
}
QualifierDefinition other = (QualifierDefinition) obj;
return this.annotations.equals(other.annotations);
}
public static QualifierDefinition forElement(AnnotatedElement element) {
if (element != null && element instanceof Field) {
Field field = (Field) element;
Set<Annotation> annotations = getQualifierAnnotations(field);
if (!annotations.isEmpty()) {
return new QualifierDefinition(field, annotations);
}
}
return null;
}
private static Set<Annotation> getQualifierAnnotations(Field field) {
// Assume that any annotations other than @MockBean/@SpyBean are qualifiers
Annotation[] candidates = field.getDeclaredAnnotations();
Set<Annotation> annotations = new HashSet<>(candidates.length);
for (Annotation candidate : candidates) {
if (!isMockOrSpyAnnotation(candidate)) {
annotations.add(candidate);
}
}
return annotations;
}
private static boolean isMockOrSpyAnnotation(Annotation candidate) {
Class<? extends Annotation> type = candidate.annotationType();
return (type.equals(MockBean.class) || type.equals(SpyBean.class)
|| AnnotationUtils.isAnnotationMetaPresent(type, MockBean.class)
|| AnnotationUtils.isAnnotationMetaPresent(type, SpyBean.class));
}
}

View File

@@ -0,0 +1,90 @@
/*
* 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.test.mock.mockito;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
import org.mockito.Mockito;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.test.context.TestContext;
import org.springframework.test.context.TestExecutionListener;
import org.springframework.test.context.support.AbstractTestExecutionListener;
/**
* {@link TestExecutionListener} to reset any mock beans that have been marked with a
* {@link MockReset}.
*
* @author Phillip Webb
* @since 1.4.0
*/
public class ResetMocksTestExecutionListener extends AbstractTestExecutionListener {
@Override
public void beforeTestMethod(TestContext testContext) throws Exception {
resetMocks(testContext.getApplicationContext(), MockReset.BEFORE);
}
@Override
public void afterTestMethod(TestContext testContext) throws Exception {
resetMocks(testContext.getApplicationContext(), MockReset.AFTER);
}
private void resetMocks(ApplicationContext applicationContext, MockReset reset) {
if (applicationContext instanceof ConfigurableApplicationContext) {
resetMocks((ConfigurableApplicationContext) applicationContext, reset);
}
}
private void resetMocks(ConfigurableApplicationContext applicationContext,
MockReset reset) {
ConfigurableListableBeanFactory beanFactory = applicationContext.getBeanFactory();
String[] names = beanFactory.getBeanDefinitionNames();
Set<String> instantiatedSingletons = new HashSet<>(
Arrays.asList(beanFactory.getSingletonNames()));
for (String name : names) {
BeanDefinition definition = beanFactory.getBeanDefinition(name);
if (definition.isSingleton() && instantiatedSingletons.contains(name)) {
Object bean = beanFactory.getSingleton(name);
if (reset.equals(MockReset.get(bean))) {
Mockito.reset(bean);
}
}
}
try {
MockitoBeans mockedBeans = beanFactory.getBean(MockitoBeans.class);
for (Object mockedBean : mockedBeans) {
if (reset.equals(MockReset.get(mockedBean))) {
Mockito.reset(mockedBean);
}
}
}
catch (NoSuchBeanDefinitionException ex) {
// Continue
}
if (applicationContext.getParent() != null) {
resetMocks(applicationContext.getParent(), reset);
}
}
}

View File

@@ -0,0 +1,146 @@
/*
* 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.test.mock.mockito;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Repeatable;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.junit.runner.RunWith;
import org.mockito.Mockito;
import org.springframework.context.ApplicationContext;
import org.springframework.core.annotation.AliasFor;
import org.springframework.test.context.junit4.SpringRunner;
/**
* Annotation that can be used to apply Mockito spies to a Spring
* {@link ApplicationContext}. Can be used as a class level annotation or on fields in
* either {@code @Configuration} classes, or test classes that are
* {@link RunWith @RunWith} the {@link SpringRunner}.
* <p>
* Spies can be applied by type or by {@link #name() bean name}. All beans in the context
* of the same type will be wrapped with the spy. If no existing bean is defined a new one
* will be added. Dependencies that are known to the application context but are not beans
* (such as those
* {@link org.springframework.beans.factory.config.ConfigurableListableBeanFactory#registerResolvableDependency(Class, Object)
* registered directly} ) will not be found and a spied bean will be added to the context
* alongside the existing dependency.
* <p>
* When {@code @SpyBean} is used on a field, as well as being registered in the
* application context, the spy will also be injected into the field. Typical usage might
* be: <pre class="code">
* &#064;RunWith(SpringRunner.class)
* public class ExampleTests {
*
* &#064;SpyBean
* private ExampleService service;
*
* &#064;Autowired
* private UserOfService userOfService;
*
* &#064;Test
* public void testUserOfService() {
* String actual = this.userOfService.makeUse();
* assertEquals("Was: Hello", actual);
* verify(this.service).greet();
* }
*
* &#064;Configuration
* &#064;Import(UserOfService.class) // A &#064;Component injected with ExampleService
* static class Config {
* }
*
*
* }
* </pre> If there is more than one bean of the requested type, qualifier metadata must be
* specified at field level: <pre class="code">
* &#064;RunWith(SpringRunner.class)
* public class ExampleTests {
*
* &#064;SpyBean
* &#064;Qualifier("example")
* private ExampleService service;
*
* ...
* }
* </pre>
* <p>
* This annotation is {@code @Repeatable} and may be specified multiple times when working
* with Java 8 or contained within a {@link SpyBeans @SpyBeans} annotation.
*
* @author Phillip Webb
* @since 1.4.0
* @see MockitoPostProcessor
*/
@Target({ ElementType.TYPE, ElementType.FIELD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Repeatable(SpyBeans.class)
public @interface SpyBean {
/**
* The name of the bean to spy. If not specified the name will either be generated or,
* if the spy is for an existing bean, the existing name will be used.
* @return the name of the bean
*/
String name() default "";
/**
* The classes to spy. This is an alias of {@link #classes()} which can be used for
* brevity if no other attributes are defined. See {@link #classes()} for details.
* @return the classes to spy
*/
@AliasFor("classes")
Class<?>[] value() default {};
/**
* The classes to spy. Each class specified here will result in a spy being applied.
* Classes can be omitted when the annotation is used on a field.
* <p>
* When {@code @SpyBean} also defines a {@code name} this attribute can only contain a
* single value.
* <p>
* If this is the only specified attribute consider using the {@code value} alias
* instead.
* @return the classes to spy
*/
@AliasFor("value")
Class<?>[] classes() default {};
/**
* The reset mode to apply to the spied bean. The default is {@link MockReset#AFTER}
* meaning that spies are automatically reset after each test method is invoked.
* @return the reset mode
*/
MockReset reset() default MockReset.AFTER;
/**
* Indicates that Mockito methods such as {@link Mockito#verify(Object) verify(mock)}
* should use the {@code target} of AOP advised beans, rather than the proxy itself.
* If set to {@code false} you may need to use the result of
* {@link org.springframework.test.util.AopTestUtils#getUltimateTargetObject(Object)
* AopTestUtils.getUltimateTargetObject(...)} when calling Mockito methods.
* @return {@code true} if the target of AOP advised beans is used or {@code false} if
* the proxy is used directly
*/
boolean proxyTargetAware() default true;
}

View File

@@ -0,0 +1,47 @@
/*
* Copyright 2012-2016 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.test.mock.mockito;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Container annotation that aggregates several {@link SpyBean} annotations.
* <p>
* Can be used natively, declaring several nested {@link SpyBean} annotations. Can also be
* used in conjunction with Java 8's support for <em>repeatable annotations</em>, where
* {@link SpyBean} can simply be declared several times on the same
* {@linkplain ElementType#TYPE type}, implicitly generating this container annotation.
*
* @author Phillip Webb
* @since 1.4.0
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
public @interface SpyBeans {
/**
* Return the contained {@link SpyBean} annotations.
* @return the spy beans
*/
SpyBean[] value();
}

View File

@@ -0,0 +1,99 @@
/*
* 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.test.mock.mockito;
import org.mockito.MockSettings;
import org.mockito.Mockito;
import org.springframework.core.ResolvableType;
import org.springframework.core.style.ToStringCreator;
import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;
/**
* A complete definition that can be used to create a Mockito spy.
*
* @author Phillip Webb
*/
class SpyDefinition extends Definition {
private static final int MULTIPLIER = 31;
private final ResolvableType typeToSpy;
SpyDefinition(String name, ResolvableType typeToSpy, MockReset reset,
boolean proxyTargetAware, QualifierDefinition qualifier) {
super(name, reset, proxyTargetAware, qualifier);
Assert.notNull(typeToSpy, "TypeToSpy must not be null");
this.typeToSpy = typeToSpy;
}
public ResolvableType getTypeToSpy() {
return this.typeToSpy;
}
@Override
public int hashCode() {
int result = super.hashCode();
result = MULTIPLIER * result + ObjectUtils.nullSafeHashCode(this.typeToSpy);
return result;
}
@Override
public boolean equals(Object obj) {
if (obj == this) {
return true;
}
if (obj == null || obj.getClass() != getClass()) {
return false;
}
SpyDefinition other = (SpyDefinition) obj;
boolean result = super.equals(obj);
result = result && ObjectUtils.nullSafeEquals(this.typeToSpy, other.typeToSpy);
return result;
}
@Override
public String toString() {
return new ToStringCreator(this).append("name", getName())
.append("typeToSpy", this.typeToSpy).append("reset", getReset())
.toString();
}
public <T> T createSpy(Object instance) {
return createSpy(getName(), instance);
}
@SuppressWarnings("unchecked")
public <T> T createSpy(String name, Object instance) {
Assert.notNull(instance, "Instance must not be null");
Assert.isInstanceOf(this.typeToSpy.resolve(), instance);
if (Mockito.mockingDetails(instance).isSpy()) {
return (T) instance;
}
MockSettings settings = MockReset.withSettings(getReset());
if (StringUtils.hasLength(name)) {
settings.name(name);
}
settings.spiedInstance(instance);
settings.defaultAnswer(Mockito.CALLS_REAL_METHODS);
return (T) Mockito.mock(instance.getClass(), settings);
}
}

View File

@@ -0,0 +1,20 @@
/*
* Copyright 2012-2016 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.
*/
/**
* Mockito integration for Spring Boot tests.
*/
package org.springframework.boot.test.mock.mockito;

View File

@@ -0,0 +1,114 @@
/*
* Copyright 2012-2016 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.test.mock.web;
import java.io.File;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import org.springframework.core.io.FileSystemResourceLoader;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;
import org.springframework.mock.web.MockServletContext;
/**
* {@link MockServletContext} implementation for Spring Boot. Respects well-known Spring
* Boot resource locations and uses an empty directory for "/" if no locations can be
* found.
*
* @author Phillip Webb
* @since 1.4.0
*/
public class SpringBootMockServletContext extends MockServletContext {
private static final String[] SPRING_BOOT_RESOURCE_LOCATIONS = new String[] {
"classpath:META-INF/resources", "classpath:resources", "classpath:static",
"classpath:public" };
private final ResourceLoader resourceLoader;
private File emptyRootFolder;
public SpringBootMockServletContext(String resourceBasePath) {
this(resourceBasePath, new FileSystemResourceLoader());
}
public SpringBootMockServletContext(String resourceBasePath,
ResourceLoader resourceLoader) {
super(resourceBasePath, resourceLoader);
this.resourceLoader = resourceLoader;
}
@Override
protected String getResourceLocation(String path) {
if (!path.startsWith("/")) {
path = "/" + path;
}
String resourceLocation = getResourceBasePathLocation(path);
if (exists(resourceLocation)) {
return resourceLocation;
}
for (String prefix : SPRING_BOOT_RESOURCE_LOCATIONS) {
resourceLocation = prefix + path;
if (exists(resourceLocation)) {
return resourceLocation;
}
}
return super.getResourceLocation(path);
}
protected final String getResourceBasePathLocation(String path) {
return super.getResourceLocation(path);
}
private boolean exists(String resourceLocation) {
try {
Resource resource = this.resourceLoader.getResource(resourceLocation);
return resource.exists();
}
catch (Exception ex) {
return false;
}
}
@Override
public URL getResource(String path) throws MalformedURLException {
URL resource = super.getResource(path);
if (resource == null && "/".equals(path)) {
// Liquibase assumes that "/" always exists, if we don't have a directory
// use a temporary location.
try {
if (this.emptyRootFolder == null) {
synchronized (this) {
File tempFolder = File.createTempFile("spr", "servlet");
tempFolder.delete();
tempFolder.mkdirs();
tempFolder.deleteOnExit();
this.emptyRootFolder = tempFolder;
}
}
return this.emptyRootFolder.toURI().toURL();
}
catch (IOException ex) {
// Ignore
}
}
return resource;
}
}

View File

@@ -0,0 +1,20 @@
/*
* Copyright 2012-2016 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.
*/
/**
* Mock web classes specific to Spring Boot.
*/
package org.springframework.boot.test.mock.web;

View File

@@ -0,0 +1,204 @@
/*
* 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.test.rule;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintStream;
import java.util.ArrayList;
import java.util.List;
import org.hamcrest.Matcher;
import org.junit.Assert;
import org.junit.rules.TestRule;
import org.junit.runner.Description;
import org.junit.runners.model.Statement;
import org.springframework.boot.ansi.AnsiOutput;
import org.springframework.boot.ansi.AnsiOutput.Enabled;
import static org.hamcrest.Matchers.allOf;
/**
* JUnit {@code @Rule} to capture output from System.out and System.err.
*
* @author Phillip Webb
* @author Andy Wilkinson
* @since 1.4.0
*/
public class OutputCapture implements TestRule {
private CaptureOutputStream captureOut;
private CaptureOutputStream captureErr;
private ByteArrayOutputStream copy;
private List<Matcher<? super String>> matchers = new ArrayList<>();
@Override
public Statement apply(final Statement base, Description description) {
return new Statement() {
@Override
public void evaluate() throws Throwable {
captureOutput();
try {
base.evaluate();
}
finally {
try {
if (!OutputCapture.this.matchers.isEmpty()) {
String output = OutputCapture.this.toString();
Assert.assertThat(output, allOf(OutputCapture.this.matchers));
}
}
finally {
releaseOutput();
}
}
}
};
}
protected void captureOutput() {
AnsiOutputControl.get().disableAnsiOutput();
this.copy = new ByteArrayOutputStream();
this.captureOut = new CaptureOutputStream(System.out, this.copy);
this.captureErr = new CaptureOutputStream(System.err, this.copy);
System.setOut(new PrintStream(this.captureOut));
System.setErr(new PrintStream(this.captureErr));
}
protected void releaseOutput() {
AnsiOutputControl.get().enabledAnsiOutput();
System.setOut(this.captureOut.getOriginal());
System.setErr(this.captureErr.getOriginal());
this.copy = null;
}
/**
* Discard all currently accumulated output.
*/
public void reset() {
this.copy.reset();
}
public void flush() {
try {
this.captureOut.flush();
this.captureErr.flush();
}
catch (IOException ex) {
// ignore
}
}
@Override
public String toString() {
flush();
return this.copy.toString();
}
/**
* Verify that the output is matched by the supplied {@code matcher}. Verification is
* performed after the test method has executed.
* @param matcher the matcher
*/
public void expect(Matcher<? super String> matcher) {
this.matchers.add(matcher);
}
private static class CaptureOutputStream extends OutputStream {
private final PrintStream original;
private final OutputStream copy;
CaptureOutputStream(PrintStream original, OutputStream copy) {
this.original = original;
this.copy = copy;
}
@Override
public void write(int b) throws IOException {
this.copy.write(b);
this.original.write(b);
this.original.flush();
}
@Override
public void write(byte[] b) throws IOException {
write(b, 0, b.length);
}
@Override
public void write(byte[] b, int off, int len) throws IOException {
this.copy.write(b, off, len);
this.original.write(b, off, len);
}
public PrintStream getOriginal() {
return this.original;
}
@Override
public void flush() throws IOException {
this.copy.flush();
this.original.flush();
}
}
/**
* Allow AnsiOutput to not be on the test classpath.
*/
private static class AnsiOutputControl {
public void disableAnsiOutput() {
}
public void enabledAnsiOutput() {
}
public static AnsiOutputControl get() {
try {
Class.forName("org.springframework.boot.ansi.AnsiOutput");
return new AnsiPresentOutputControl();
}
catch (ClassNotFoundException ex) {
return new AnsiOutputControl();
}
}
}
private static class AnsiPresentOutputControl extends AnsiOutputControl {
@Override
public void disableAnsiOutput() {
AnsiOutput.setEnabled(Enabled.NEVER);
}
@Override
public void enabledAnsiOutput() {
AnsiOutput.setEnabled(Enabled.DETECT);
}
}
}

View File

@@ -0,0 +1,20 @@
/*
* Copyright 2012-2016 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.
*/
/**
* Useful JUnit {@code @Rule} classes.
*/
package org.springframework.boot.test.rule;

View File

@@ -0,0 +1,43 @@
/*
* Copyright 2012-2016 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.test.util;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ConfigurableApplicationContext;
/**
* Application context related test utilities.
*
* @author Stephane Nicoll
* @since 1.4.0
*/
public abstract class ApplicationContextTestUtils {
/**
* Closes this {@link ApplicationContext} and its parent hierarchy if any.
* @param context the context to close (can be {@code null})
*/
public static void closeAll(ApplicationContext context) {
if (context != null) {
if (context instanceof ConfigurableApplicationContext) {
((ConfigurableApplicationContext) context).close();
}
closeAll(context.getParent());
}
}
}

View File

@@ -0,0 +1,105 @@
/*
* 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.test.util;
import java.util.HashMap;
import java.util.Map;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.Environment;
import org.springframework.core.env.MapPropertySource;
import org.springframework.core.env.MutablePropertySources;
/**
* Test utilities for setting environment values.
*
* @author Dave Syer
* @author Stephane Nicoll
* @since 1.4.0
* @deprecated since 2.0.0 in favor of {@link TestPropertyValues}
*/
@Deprecated
public abstract class EnvironmentTestUtils {
/**
* Add additional (high priority) values to an {@link Environment} owned by an
* {@link ApplicationContext}. Name-value pairs can be specified with colon (":") or
* equals ("=") separators.
* @param context the context with an environment to modify
* @param pairs the name:value pairs
*/
public static void addEnvironment(ConfigurableApplicationContext context,
String... pairs) {
addEnvironment(context.getEnvironment(), pairs);
}
/**
* Add additional (high priority) values to an {@link Environment}. Name-value pairs
* can be specified with colon (":") or equals ("=") separators.
* @param environment the environment to modify
* @param pairs the name:value pairs
*/
public static void addEnvironment(ConfigurableEnvironment environment,
String... pairs) {
addEnvironment("test", environment, pairs);
}
/**
* Add additional (high priority) values to an {@link Environment}. Name-value pairs
* can be specified with colon (":") or equals ("=") separators.
* @param environment the environment to modify
* @param name the property source name
* @param pairs the name:value pairs
*/
public static void addEnvironment(String name, ConfigurableEnvironment environment,
String... pairs) {
MutablePropertySources sources = environment.getPropertySources();
Map<String, Object> map = getOrAdd(sources, name);
for (String pair : pairs) {
int index = getSeparatorIndex(pair);
String key = (index > 0 ? pair.substring(0, index) : pair);
String value = (index > 0 ? pair.substring(index + 1) : "");
map.put(key.trim(), value.trim());
}
}
@SuppressWarnings("unchecked")
private static Map<String, Object> getOrAdd(MutablePropertySources sources,
String name) {
if (sources.contains(name)) {
return (Map<String, Object>) sources.get(name).getSource();
}
Map<String, Object> map = new HashMap<>();
sources.addFirst(new MapPropertySource(name, map));
return map;
}
private static int getSeparatorIndex(String pair) {
int colonIndex = pair.indexOf(":");
int equalIndex = pair.indexOf("=");
if (colonIndex == -1) {
return equalIndex;
}
if (equalIndex == -1) {
return colonIndex;
}
return Math.min(colonIndex, equalIndex);
}
}

View File

@@ -0,0 +1,314 @@
/*
* 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.test.util;
import java.io.Closeable;
import java.util.Arrays;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.Callable;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;
import org.springframework.boot.context.properties.source.ConfigurationPropertySources;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.Environment;
import org.springframework.core.env.MapPropertySource;
import org.springframework.core.env.MutablePropertySources;
import org.springframework.core.env.PropertySource;
import org.springframework.core.env.SystemEnvironmentPropertySource;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
/**
* Test utilities for adding properties. Properties can be applied to a Spring
* {@link Environment} or to the {@link System#getProperties() system environment}.
*
* @author Madhura Bhave
* @author Phillip Webb
* @author Stephane Nicoll
* @since 2.0.0
*/
public final class TestPropertyValues {
private static final TestPropertyValues EMPTY = new TestPropertyValues(
Collections.emptyMap());
private final Map<String, Object> properties;
private TestPropertyValues(Map<String, Object> properties) {
this.properties = Collections.unmodifiableMap(properties);
}
/**
* Builder method to add more properties.
* @param pairs the property pairs to add
* @return a new {@link TestPropertyValues} instance
*/
public TestPropertyValues and(String... pairs) {
return and(Arrays.stream(pairs).map(Pair::parse));
}
private TestPropertyValues and(Stream<Pair> pairs) {
Map<String, Object> properties = new LinkedHashMap<>(this.properties);
pairs.filter(Objects::nonNull).forEach((pair) -> pair.addTo(properties));
return new TestPropertyValues(properties);
}
/**
* Add the properties from the underlying map to the environment owned by an
* {@link ApplicationContext}.
* @param context the context with an environment to modify
*/
public void applyTo(ConfigurableApplicationContext context) {
applyTo(context.getEnvironment());
}
/**
* Add the properties from the underlying map to the environment. The default property
* source used is {@link MapPropertySource}.
* @param environment the environment that needs to be modified
*/
public void applyTo(ConfigurableEnvironment environment) {
applyTo(environment, Type.MAP);
}
/**
* Add the properties from the underlying map to the environment using the specified
* property source type.
* @param environment the environment that needs to be modified
* @param type the type of {@link PropertySource} to be added. See {@link Type}
*/
public void applyTo(ConfigurableEnvironment environment, Type type) {
applyTo(environment, type, "test");
}
/**
* Add the properties from the underlying map to the environment using the specified
* property source type and name.
* @param environment the environment that needs to be modified
* @param type the type of {@link PropertySource} to be added. See {@link Type}
* @param name the name for the property source
*/
public void applyTo(ConfigurableEnvironment environment, Type type, String name) {
Assert.notNull(environment, "Environment must not be null");
Assert.notNull(type, "Property source type must not be null");
Assert.notNull(name, "Property source name must not be null");
MutablePropertySources sources = environment.getPropertySources();
addToSources(sources, type, name);
ConfigurationPropertySources.attach(environment);
}
/**
* Add the properties to the {@link System#getProperties() system properties} for the
* duration of the {@code call}, restoring previous values when the call completes.
* @param <T> the result type
* @param call the call to make
* @return the result of the call
*/
public <T> T applyToSystemProperties(Callable<T> call) {
try (SystemPropertiesHandler handler = new SystemPropertiesHandler()) {
return call.call();
}
catch (Exception ex) {
rethrow(ex);
throw new IllegalStateException("Original cause not rethrown", ex);
}
}
@SuppressWarnings("unchecked")
private <E extends Throwable> void rethrow(Throwable e) throws E {
throw (E) e;
}
@SuppressWarnings("unchecked")
private void addToSources(MutablePropertySources sources, Type type, String name) {
if (sources.contains(name)) {
PropertySource<?> propertySource = sources.get(name);
if (propertySource.getClass().equals(type.getSourceClass())) {
((Map<String, Object>) propertySource.getSource())
.putAll(this.properties);
return;
}
}
Map<String, Object> source = new LinkedHashMap<>(this.properties);
sources.addFirst((type.equals(Type.MAP) ? new MapPropertySource(name, source)
: new SystemEnvironmentPropertySource(name, source)));
}
/**
* Return a new {@link TestPropertyValues} with the underlying map populated with the
* given property pairs. Name-value pairs can be specified with colon (":") or equals
* ("=") separators.
* @param pairs the name-value pairs for properties that need to be added to the
* environment
* @return the new instance
*/
public static TestPropertyValues of(String... pairs) {
return of(Stream.of(pairs));
}
/**
* Return a new {@link TestPropertyValues} with the underlying map populated with the
* given property pairs. Name-value pairs can be specified with colon (":") or equals
* ("=") separators.
* @param pairs the name-value pairs for properties that need to be added to the
* environment
* @return the new instance
*/
public static TestPropertyValues of(Iterable<String> pairs) {
if (pairs == null) {
return empty();
}
return of(StreamSupport.stream(pairs.spliterator(), false));
}
/**
* Return a new {@link TestPropertyValues} with the underlying map populated with the
* given property pairs. Name-value pairs can be specified with colon (":") or equals
* ("=") separators.
* @param pairs the name-value pairs for properties that need to be added to the
* environment
* @return the new instance
*/
public static TestPropertyValues of(Stream<String> pairs) {
if (pairs == null) {
return empty();
}
return empty().and(pairs.map(Pair::parse));
}
/**
* Return an empty {@link TestPropertyValues} instance.
* @return an empty instance
*/
public static TestPropertyValues empty() {
return EMPTY;
}
/**
* The type of property source.
*/
public enum Type {
/**
* Used for {@link SystemEnvironmentPropertySource}.
*/
SYSTEM(SystemEnvironmentPropertySource.class),
/**
* Used for {@link MapPropertySource}.
*/
MAP(MapPropertySource.class);
private Class<? extends MapPropertySource> sourceClass;
Type(Class<? extends MapPropertySource> sourceClass) {
this.sourceClass = sourceClass;
}
public Class<? extends MapPropertySource> getSourceClass() {
return this.sourceClass;
}
}
/**
* A single name value pair.
*/
public static class Pair {
private String name;
private String value;
public Pair(String name, String value) {
Assert.hasLength(name, "Name must not be empty");
this.name = name;
this.value = value;
}
public void addTo(Map<String, Object> properties) {
properties.put(this.name, this.value);
}
public static Pair parse(String pair) {
int index = getSeparatorIndex(pair);
String name = (index > 0 ? pair.substring(0, index) : pair);
String value = (index > 0 ? pair.substring(index + 1) : "");
return of(name.trim(), value.trim());
}
private static int getSeparatorIndex(String pair) {
int colonIndex = pair.indexOf(":");
int equalIndex = pair.indexOf("=");
if (colonIndex == -1) {
return equalIndex;
}
if (equalIndex == -1) {
return colonIndex;
}
return Math.min(colonIndex, equalIndex);
}
private static Pair of(String name, String value) {
if (StringUtils.isEmpty(name) && StringUtils.isEmpty(value)) {
return null;
}
return new Pair(name, value);
}
}
/**
* Handler to apply and restore system properties.
*/
private class SystemPropertiesHandler implements Closeable {
private final Map<String, String> previous;
SystemPropertiesHandler() {
this.previous = apply(TestPropertyValues.this.properties);
}
private Map<String, String> apply(Map<String, ?> properties) {
Map<String, String> previous = new LinkedHashMap<>();
properties.forEach((name, value) -> previous.put(name,
setOrClear(name, (String) value)));
return previous;
}
@Override
public void close() {
this.previous.forEach(this::setOrClear);
}
private String setOrClear(String name, String value) {
Assert.notNull(name, "Name must not be null");
if (StringUtils.isEmpty(value)) {
return (String) System.getProperties().remove(name);
}
return (String) System.getProperties().setProperty(name, value);
}
}
}

View File

@@ -0,0 +1,20 @@
/*
* Copyright 2012-2016 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.
*/
/**
* General purpose test utilities.
*/
package org.springframework.boot.test.util;

View File

@@ -0,0 +1,76 @@
/*
* 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.test.web.client;
import org.springframework.boot.web.client.RootUriTemplateHandler;
import org.springframework.core.env.Environment;
import org.springframework.util.Assert;
import org.springframework.web.util.DefaultUriBuilderFactory;
import org.springframework.web.util.UriTemplateHandler;
/**
* {@link UriTemplateHandler} will automatically prefix relative URIs with
* <code>localhost:$&#123;local.server.port&#125;</code>.
*
* @author Phillip Webb
* @author Andy Wilkinson
* @author Eddú Meléndez
* @author Madhura Bhave
* @since 1.4.0
*/
public class LocalHostUriTemplateHandler extends RootUriTemplateHandler {
private final Environment environment;
private final String scheme;
private final String prefix = "server.servlet.";
/**
* Create a new {@code LocalHostUriTemplateHandler} that will generate {@code http}
* URIs using the given {@code environment} to determine the context path and port.
* @param environment the environment used to determine the port
*/
public LocalHostUriTemplateHandler(Environment environment) {
this(environment, "http");
}
/**
* Create a new {@code LocalHostUriTemplateHandler} that will generate URIs with the
* given {@code scheme} and use the given {@code environment} to determine the
* context-path and port.
* @param environment the environment used to determine the port
* @param scheme the scheme of the root uri
* @since 1.4.1
*/
public LocalHostUriTemplateHandler(Environment environment, String scheme) {
super(new DefaultUriBuilderFactory());
Assert.notNull(environment, "Environment must not be null");
Assert.notNull(scheme, "Scheme must not be null");
this.environment = environment;
this.scheme = scheme;
}
@Override
public String getRootUri() {
String port = this.environment.getProperty("local.server.port", "8080");
String contextPath = this.environment.getProperty(this.prefix + "context-path",
"");
return this.scheme + "://localhost:" + port + contextPath;
}
}

View File

@@ -0,0 +1,124 @@
/*
* 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.test.web.client;
import java.util.Collections;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import org.springframework.beans.BeanUtils;
import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.boot.web.client.RestTemplateCustomizer;
import org.springframework.test.web.client.MockRestServiceServer;
import org.springframework.test.web.client.RequestExpectationManager;
import org.springframework.test.web.client.SimpleRequestExpectationManager;
import org.springframework.util.Assert;
import org.springframework.web.client.RestTemplate;
/**
* {@link RestTemplateCustomizer} that can be applied to a {@link RestTemplateBuilder}
* instances to add {@link MockRestServiceServer} support.
* <p>
* Typically applied to an existing builder before it is used, for example:
* <pre class="code">
* MockServerRestTemplateCustomizer customizer = new MockServerRestTemplateCustomizer();
* MyBean bean = new MyBean(new RestTemplateBuilder(customizer));
* customizer.getServer().expect(requestTo("/hello")).andRespond(withSuccess());
* bean.makeRestCall();
* </pre>
* <p>
* If the customizer is only used once, the {@link #getServer()} method can be used to
* obtain the mock server. If the customizer has been used more than once the
* {@link #getServer(RestTemplate)} or {@link #getServers()} method must be used to access
* the related server.
*
* @author Phillip Webb
* @since 1.4.0
* @see #getServer()
* @see #getServer(RestTemplate)
*/
public class MockServerRestTemplateCustomizer implements RestTemplateCustomizer {
private Map<RestTemplate, RequestExpectationManager> expectationManagers = new ConcurrentHashMap<>();
private Map<RestTemplate, MockRestServiceServer> servers = new ConcurrentHashMap<>();
private final Class<? extends RequestExpectationManager> expectationManager;
private boolean detectRootUri = true;
public MockServerRestTemplateCustomizer() {
this.expectationManager = SimpleRequestExpectationManager.class;
}
public MockServerRestTemplateCustomizer(
Class<? extends RequestExpectationManager> expectationManager) {
Assert.notNull(expectationManager, "ExpectationManager must not be null");
this.expectationManager = expectationManager;
}
/**
* Set if root URIs from {@link RootUriRequestExpectationManager} should be detected
* and applied to the {@link MockRestServiceServer}.
* @param detectRootUri if root URIs should be detected
*/
public void setDetectRootUri(boolean detectRootUri) {
this.detectRootUri = detectRootUri;
}
@Override
public void customize(RestTemplate restTemplate) {
RequestExpectationManager expectationManager = createExpectationManager();
if (this.detectRootUri) {
expectationManager = RootUriRequestExpectationManager
.forRestTemplate(restTemplate, expectationManager);
}
MockRestServiceServer server = MockRestServiceServer.bindTo(restTemplate)
.build(expectationManager);
this.expectationManagers.put(restTemplate, expectationManager);
this.servers.put(restTemplate, server);
}
protected RequestExpectationManager createExpectationManager() {
return BeanUtils.instantiateClass(this.expectationManager);
}
public MockRestServiceServer getServer() {
Assert.state(!this.servers.isEmpty(),
"Unable to return a single MockRestServiceServer since "
+ "MockServerRestTemplateCustomizer has not been bound to "
+ "a RestTemplate");
Assert.state(this.servers.size() == 1,
"Unable to return a single MockRestServiceServer since "
+ "MockServerRestTemplateCustomizer has been bound to "
+ "more than one RestTemplate");
return this.servers.values().iterator().next();
}
public Map<RestTemplate, RequestExpectationManager> getExpectationManagers() {
return this.expectationManagers;
}
public MockRestServiceServer getServer(RestTemplate restTemplate) {
return this.servers.get(restTemplate);
}
public Map<RestTemplate, MockRestServiceServer> getServers() {
return Collections.unmodifiableMap(this.servers);
}
}

View File

@@ -0,0 +1,202 @@
/*
* Copyright 2012-2016 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.test.web.client;
import java.io.IOException;
import java.io.OutputStream;
import java.net.URI;
import java.net.URISyntaxException;
import org.springframework.boot.web.client.RootUriTemplateHandler;
import org.springframework.http.client.ClientHttpRequest;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.http.client.support.HttpRequestWrapper;
import org.springframework.mock.http.client.MockClientHttpRequest;
import org.springframework.test.web.client.ExpectedCount;
import org.springframework.test.web.client.MockRestServiceServer;
import org.springframework.test.web.client.MockRestServiceServer.MockRestServiceServerBuilder;
import org.springframework.test.web.client.RequestExpectationManager;
import org.springframework.test.web.client.RequestMatcher;
import org.springframework.test.web.client.ResponseActions;
import org.springframework.test.web.client.SimpleRequestExpectationManager;
import org.springframework.util.Assert;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.util.UriTemplateHandler;
/**
* {@link RequestExpectationManager} that strips the specified root URI from the request
* before verification. Can be used to simply test declarations when all REST calls start
* the same way. For example: <pre class="code">
* RestTemplate restTemplate = new RestTemplateBuilder().rootUri("http://example.com").build();
* MockRestServiceServer server = RootUriRequestExpectationManager.bindTo(restTemplate);
* server.expect(requestTo("/hello")).andRespond(withSuccess());
* restTemplate.getForEntity("/hello", String.class);
* </pre>
*
* @author Phillip Webb
* @since 1.4.0
* @see RootUriTemplateHandler
* @see #bindTo(RestTemplate)
* @see #forRestTemplate(RestTemplate, RequestExpectationManager)
*/
public class RootUriRequestExpectationManager implements RequestExpectationManager {
private final String rootUri;
private final RequestExpectationManager expectationManager;
public RootUriRequestExpectationManager(String rootUri,
RequestExpectationManager expectationManager) {
Assert.notNull(rootUri, "RootUri must not be null");
Assert.notNull(expectationManager, "ExpectationManager must not be null");
this.rootUri = rootUri;
this.expectationManager = expectationManager;
}
@Override
public ResponseActions expectRequest(ExpectedCount count,
RequestMatcher requestMatcher) {
return this.expectationManager.expectRequest(count, requestMatcher);
}
@Override
public ClientHttpResponse validateRequest(ClientHttpRequest request)
throws IOException {
String uri = request.getURI().toString();
if (uri.startsWith(this.rootUri)) {
request = replaceURI(request, uri.substring(this.rootUri.length()));
}
try {
return this.expectationManager.validateRequest(request);
}
catch (AssertionError ex) {
String message = ex.getMessage();
String prefix = "Request URI expected:</";
if (message != null && message.startsWith(prefix)) {
throw new AssertionError("Request URI expected:<" + this.rootUri
+ message.substring(prefix.length() - 1));
}
throw ex;
}
}
private ClientHttpRequest replaceURI(ClientHttpRequest request,
String replacementUri) {
URI uri;
try {
uri = new URI(replacementUri);
if (request instanceof MockClientHttpRequest) {
((MockClientHttpRequest) request).setURI(uri);
return request;
}
return new ReplaceUriClientHttpRequest(uri, request);
}
catch (URISyntaxException ex) {
throw new IllegalStateException(ex);
}
}
@Override
public void verify() {
this.expectationManager.verify();
}
@Override
public void reset() {
this.expectationManager.reset();
}
/**
* Return a bound {@link MockRestServiceServer} for the given {@link RestTemplate},
* configured with {@link RootUriRequestExpectationManager} when possible.
* @param restTemplate the source REST template
* @return a configured {@link MockRestServiceServer}
*/
public static MockRestServiceServer bindTo(RestTemplate restTemplate) {
return bindTo(restTemplate, new SimpleRequestExpectationManager());
}
/**
* Return a bound {@link MockRestServiceServer} for the given {@link RestTemplate},
* configured with {@link RootUriRequestExpectationManager} when possible.
* @param restTemplate the source REST template
* @param expectationManager the source {@link RequestExpectationManager}
* @return a configured {@link MockRestServiceServer}
*/
public static MockRestServiceServer bindTo(RestTemplate restTemplate,
RequestExpectationManager expectationManager) {
MockRestServiceServerBuilder builder = MockRestServiceServer.bindTo(restTemplate);
return builder.build(forRestTemplate(restTemplate, expectationManager));
}
/**
* Return {@link RequestExpectationManager} to be used for binding with the specified
* {@link RestTemplate}. If the {@link RestTemplate} is using a
* {@link RootUriTemplateHandler} then a {@link RootUriRequestExpectationManager} is
* returned, otherwise the source manager is returned unchanged.
* @param restTemplate the source REST template
* @param expectationManager the source {@link RequestExpectationManager}
* @return a {@link RequestExpectationManager} to be bound to the template
*/
public static RequestExpectationManager forRestTemplate(RestTemplate restTemplate,
RequestExpectationManager expectationManager) {
Assert.notNull(restTemplate, "RestTemplate must not be null");
UriTemplateHandler templateHandler = restTemplate.getUriTemplateHandler();
if (templateHandler instanceof RootUriTemplateHandler) {
return new RootUriRequestExpectationManager(
((RootUriTemplateHandler) templateHandler).getRootUri(),
expectationManager);
}
return expectationManager;
}
/**
* {@link ClientHttpRequest} wrapper to replace the request URI.
*/
private static class ReplaceUriClientHttpRequest extends HttpRequestWrapper
implements ClientHttpRequest {
private final URI uri;
ReplaceUriClientHttpRequest(URI uri, ClientHttpRequest request) {
super(request);
this.uri = uri;
}
@Override
public URI getURI() {
return this.uri;
}
@Override
public OutputStream getBody() throws IOException {
return getRequest().getBody();
}
@Override
public ClientHttpResponse execute() throws IOException {
return getRequest().execute();
}
@Override
public ClientHttpRequest getRequest() {
return (ClientHttpRequest) super.getRequest();
}
}
}

View File

@@ -0,0 +1,20 @@
/*
* Copyright 2012-2016 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.
*/
/**
* Web client test utilities.
*/
package org.springframework.boot.test.web.client;

View File

@@ -0,0 +1,55 @@
/*
* Copyright 2012-2016 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.test.web.htmlunit;
import java.io.IOException;
import java.net.MalformedURLException;
import com.gargoylesoftware.htmlunit.FailingHttpStatusCodeException;
import com.gargoylesoftware.htmlunit.Page;
import com.gargoylesoftware.htmlunit.WebClient;
import org.springframework.core.env.Environment;
import org.springframework.util.Assert;
/**
* {@link WebClient} will automatically prefix relative URLs with
* <code>localhost:$&#123;local.server.port&#125;</code>.
*
* @author Phillip Webb
* @since 1.4.0
*/
public class LocalHostWebClient extends WebClient {
private final Environment environment;
public LocalHostWebClient(Environment environment) {
Assert.notNull(environment, "Environment must not be null");
this.environment = environment;
}
@Override
public <P extends Page> P getPage(String url)
throws IOException, FailingHttpStatusCodeException, MalformedURLException {
if (url.startsWith("/")) {
String port = this.environment.getProperty("local.server.port", "8080");
url = "http://localhost:" + port + url;
}
return super.getPage(url);
}
}

View File

@@ -0,0 +1,20 @@
/*
* Copyright 2012-2016 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.
*/
/**
* HtmlUnit support classes.
*/
package org.springframework.boot.test.web.htmlunit;

View File

@@ -0,0 +1,72 @@
/*
* Copyright 2012-2016 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.test.web.htmlunit.webdriver;
import com.gargoylesoftware.htmlunit.BrowserVersion;
import org.openqa.selenium.Capabilities;
import org.springframework.core.env.Environment;
import org.springframework.test.web.servlet.htmlunit.webdriver.WebConnectionHtmlUnitDriver;
import org.springframework.util.Assert;
/**
* {@link LocalHostWebConnectionHtmlUnitDriver} will automatically prefix relative URLs
* with <code>localhost:$&#123;local.server.port&#125;</code>.
*
* @author Phillip Webb
* @since 1.4.0
*/
public class LocalHostWebConnectionHtmlUnitDriver extends WebConnectionHtmlUnitDriver {
private final Environment environment;
public LocalHostWebConnectionHtmlUnitDriver(Environment environment) {
Assert.notNull(environment, "Environment must not be null");
this.environment = environment;
}
public LocalHostWebConnectionHtmlUnitDriver(Environment environment,
boolean enableJavascript) {
super(enableJavascript);
Assert.notNull(environment, "Environment must not be null");
this.environment = environment;
}
public LocalHostWebConnectionHtmlUnitDriver(Environment environment,
BrowserVersion browserVersion) {
super(browserVersion);
Assert.notNull(environment, "Environment must not be null");
this.environment = environment;
}
public LocalHostWebConnectionHtmlUnitDriver(Environment environment,
Capabilities capabilities) {
super(capabilities);
Assert.notNull(environment, "Environment must not be null");
this.environment = environment;
}
@Override
public void get(String url) {
if (url.startsWith("/")) {
String port = this.environment.getProperty("local.server.port", "8080");
url = "http://localhost:" + port + url;
}
super.get(url);
}
}

View File

@@ -0,0 +1,20 @@
/*
* Copyright 2012-2016 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.
*/
/**
* Selenium support classes.
*/
package org.springframework.boot.test.web.htmlunit.webdriver;

View File

@@ -0,0 +1,150 @@
/*
* 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.test.web.reactive;
import java.util.Collection;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.web.codec.CodecCustomizer;
import org.springframework.boot.web.reactive.server.AbstractReactiveWebServerFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.test.context.ContextCustomizer;
import org.springframework.test.context.MergedContextConfiguration;
import org.springframework.test.web.reactive.server.WebTestClient;
import org.springframework.util.CollectionUtils;
import org.springframework.web.reactive.function.client.ExchangeStrategies;
/**
* {@link ContextCustomizer} for {@link WebTestClient}.
*
* @author Stephane Nicoll
*/
class WebTestClientContextCustomizer implements ContextCustomizer {
@Override
public void customizeContext(ConfigurableApplicationContext context,
MergedContextConfiguration mergedConfig) {
SpringBootTest annotation = AnnotatedElementUtils
.getMergedAnnotation(mergedConfig.getTestClass(), SpringBootTest.class);
if (annotation.webEnvironment().isEmbedded()) {
registerWebTestClient(context);
}
}
private void registerWebTestClient(ConfigurableApplicationContext context) {
ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
if (beanFactory instanceof BeanDefinitionRegistry) {
registerWebTestClient(context, (BeanDefinitionRegistry) context);
}
}
private void registerWebTestClient(ConfigurableApplicationContext context,
BeanDefinitionRegistry registry) {
registry.registerBeanDefinition(WebTestClient.class.getName(),
new RootBeanDefinition(WebTestClientFactory.class));
}
@Override
public int hashCode() {
return getClass().hashCode();
}
@Override
public boolean equals(Object obj) {
return (obj != null && obj.getClass().equals(getClass()));
}
/**
* {@link FactoryBean} used to create and configure a {@link WebTestClient}.
*/
public static class WebTestClientFactory
implements FactoryBean<WebTestClient>, ApplicationContextAware {
private ApplicationContext applicationContext;
private WebTestClient object;
@Override
public void setApplicationContext(ApplicationContext applicationContext)
throws BeansException {
this.applicationContext = applicationContext;
}
@Override
public boolean isSingleton() {
return true;
}
@Override
public Class<?> getObjectType() {
return WebTestClient.class;
}
@Override
public WebTestClient getObject() throws Exception {
if (this.object == null) {
this.object = createWebTestClient();
}
return this.object;
}
private WebTestClient createWebTestClient() {
boolean sslEnabled = isSslEnabled(this.applicationContext);
String port = this.applicationContext.getEnvironment()
.getProperty("local.server.port", "8080");
String baseUrl = (sslEnabled ? "https" : "http") + "://localhost:" + port;
WebTestClient.Builder builder = WebTestClient.bindToServer();
customizeWebTestClientCodecs(builder, this.applicationContext);
return builder.baseUrl(baseUrl).build();
}
private boolean isSslEnabled(ApplicationContext context) {
try {
AbstractReactiveWebServerFactory webServerFactory = context
.getBean(AbstractReactiveWebServerFactory.class);
return webServerFactory.getSsl() != null
&& webServerFactory.getSsl().isEnabled();
}
catch (NoSuchBeanDefinitionException ex) {
return false;
}
}
private void customizeWebTestClientCodecs(WebTestClient.Builder clientBuilder,
ApplicationContext context) {
Collection<CodecCustomizer> codecCustomizers = context
.getBeansOfType(CodecCustomizer.class).values();
if (!CollectionUtils.isEmpty(codecCustomizers)) {
clientBuilder.exchangeStrategies(ExchangeStrategies.builder()
.codecs((codecs) -> codecCustomizers.forEach(
(codecCustomizer) -> codecCustomizer.customize(codecs)))
.build());
}
}
}
}

View File

@@ -0,0 +1,51 @@
/*
* 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.test.web.reactive;
import java.util.List;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.test.context.ContextConfigurationAttributes;
import org.springframework.test.context.ContextCustomizer;
import org.springframework.test.context.ContextCustomizerFactory;
import org.springframework.util.ClassUtils;
/**
* {@link ContextCustomizerFactory} for {@code WebTestClient}.
*
* @author Stephane Nicoll
*/
public class WebTestClientContextCustomizerFactory implements ContextCustomizerFactory {
private static final String WEB_TEST_CLIENT_CLASS = "org.springframework.web.reactive.function.client.WebClient";
@Override
public ContextCustomizer createContextCustomizer(Class<?> testClass,
List<ContextConfigurationAttributes> configAttributes) {
if (isWebClientPresent() && AnnotatedElementUtils.findMergedAnnotation(testClass,
SpringBootTest.class) != null) {
return new WebTestClientContextCustomizer();
}
return null;
}
private boolean isWebClientPresent() {
return ClassUtils.isPresent(WEB_TEST_CLIENT_CLASS, getClass().getClassLoader());
}
}

View File

@@ -0,0 +1,13 @@
# Spring Test ContextCustomizerFactories
org.springframework.test.context.ContextCustomizerFactory=\
org.springframework.boot.test.context.ImportsContextCustomizerFactory,\
org.springframework.boot.test.context.SpringBootTestContextCustomizerFactory,\
org.springframework.boot.test.context.filter.ExcludeFilterContextCustomizerFactory,\
org.springframework.boot.test.json.DuplicateJsonObjectContextCustomizerFactory,\
org.springframework.boot.test.mock.mockito.MockitoContextCustomizerFactory,\
org.springframework.boot.test.web.reactive.WebTestClientContextCustomizerFactory
# Test Execution Listeners
org.springframework.test.context.TestExecutionListener=\
org.springframework.boot.test.mock.mockito.MockitoTestExecutionListener,\
org.springframework.boot.test.mock.mockito.ResetMocksTestExecutionListener

View File

@@ -0,0 +1,120 @@
/*
* 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.test.context;
import org.junit.Test;
import reactor.core.publisher.Mono;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.test.web.client.TestRestTemplate;
import org.springframework.boot.web.embedded.tomcat.TomcatReactiveWebServerFactory;
import org.springframework.boot.web.reactive.context.ReactiveWebApplicationContext;
import org.springframework.boot.web.reactive.server.ReactiveWebServerFactory;
import org.springframework.boot.web.server.LocalServerPort;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.support.PropertySourcesPlaceholderConfigurer;
import org.springframework.http.server.reactive.HttpHandler;
import org.springframework.test.web.reactive.server.WebTestClient;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.server.adapter.WebHttpHandlerBuilder;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Base class for {@link SpringBootTest} tests configured to start an embedded reactive
* container.
*
* @author Stephane Nicoll
*/
public abstract class AbstractSpringBootTestEmbeddedReactiveWebEnvironmentTests {
@LocalServerPort
private int port = 0;
@Value("${value}")
private int value = 0;
@Autowired
private ReactiveWebApplicationContext context;
@Autowired
private WebTestClient webClient;
@Autowired
private TestRestTemplate restTemplate;
public ReactiveWebApplicationContext getContext() {
return this.context;
}
@Test
public void runAndTestHttpEndpoint() {
assertThat(this.port).isNotEqualTo(8080).isNotEqualTo(0);
WebTestClient.bindToServer().baseUrl("http://localhost:" + this.port).build()
.get().uri("/").exchange().expectBody(String.class)
.isEqualTo("Hello World");
}
@Test
public void injectWebTestClient() {
this.webClient.get().uri("/").exchange().expectBody(String.class)
.isEqualTo("Hello World");
}
@Test
public void injectTestRestTemplate() {
String body = this.restTemplate.getForObject("/", String.class);
assertThat(body).isEqualTo("Hello World");
}
@Test
public void annotationAttributesOverridePropertiesFile() throws Exception {
assertThat(this.value).isEqualTo(123);
}
protected static class AbstractConfig {
@Value("${server.port:8080}")
private int port = 8080;
@Bean
public HttpHandler httpHandler(ApplicationContext applicationContext) {
return WebHttpHandlerBuilder.applicationContext(applicationContext).build();
}
@Bean
public ReactiveWebServerFactory webServerFactory() {
TomcatReactiveWebServerFactory factory = new TomcatReactiveWebServerFactory();
factory.setPort(this.port);
return factory;
}
@Bean
public static PropertySourcesPlaceholderConfigurer propertyPlaceholder() {
return new PropertySourcesPlaceholderConfigurer();
}
@RequestMapping("/")
public Mono<String> home() {
return Mono.just("Hello World");
}
}
}

View File

@@ -0,0 +1,124 @@
/*
* 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.test.context;
import javax.servlet.ServletContext;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.test.web.client.TestRestTemplate;
import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory;
import org.springframework.boot.web.server.LocalServerPort;
import org.springframework.boot.web.servlet.server.ServletWebServerFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.support.PropertySourcesPlaceholderConfigurer;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.support.WebApplicationContextUtils;
import org.springframework.web.servlet.DispatcherServlet;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Base class for {@link SpringBootTest} tests configured to start an embedded web server.
*
* @author Phillip Webb
* @author Andy Wilkinson
*/
public abstract class AbstractSpringBootTestWebServerWebEnvironmentTests {
@LocalServerPort
private int port = 0;
@Value("${value}")
private int value = 0;
@Autowired
private WebApplicationContext context;
@Autowired
private ServletContext servletContext;
@Autowired
private TestRestTemplate restTemplate;
public WebApplicationContext getContext() {
return this.context;
}
public TestRestTemplate getRestTemplate() {
return this.restTemplate;
}
@Test
public void runAndTestHttpEndpoint() {
assertThat(this.port).isNotEqualTo(8080).isNotEqualTo(0);
String body = new RestTemplate()
.getForObject("http://localhost:" + this.port + "/", String.class);
assertThat(body).isEqualTo("Hello World");
}
@Test
public void injectTestRestTemplate() {
String body = this.restTemplate.getForObject("/", String.class);
assertThat(body).isEqualTo("Hello World");
}
@Test
public void annotationAttributesOverridePropertiesFile() throws Exception {
assertThat(this.value).isEqualTo(123);
}
@Test
public void validateWebApplicationContextIsSet() {
assertThat(this.context).isSameAs(
WebApplicationContextUtils.getWebApplicationContext(this.servletContext));
}
protected static class AbstractConfig {
@Value("${server.port:8080}")
private int port = 8080;
@Bean
public DispatcherServlet dispatcherServlet() {
return new DispatcherServlet();
}
@Bean
public ServletWebServerFactory webServerFactory() {
TomcatServletWebServerFactory factory = new TomcatServletWebServerFactory();
factory.setPort(this.port);
return factory;
}
@Bean
public static PropertySourcesPlaceholderConfigurer propertyPlaceholder() {
return new PropertySourcesPlaceholderConfigurer();
}
@RequestMapping("/")
public String home() {
return "Hello World";
}
}
}

View File

@@ -0,0 +1,54 @@
/*
* Copyright 2012-2016 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.test.context;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.Environment;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringRunner;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link ConfigFileApplicationContextInitializer}.
*
* @author Phillip Webb
*/
@RunWith(SpringRunner.class)
@DirtiesContext
@ContextConfiguration(classes = ConfigFileApplicationContextInitializerTests.Config.class, initializers = ConfigFileApplicationContextInitializer.class)
public class ConfigFileApplicationContextInitializerTests {
@Autowired
private Environment environment;
@Test
public void initializerPopulatesEnvironment() {
assertThat(this.environment.getProperty("foo")).isEqualTo("bucket");
}
@Configuration
public static class Config {
}
}

View File

@@ -0,0 +1,63 @@
/*
* Copyright 2012-2016 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.test.context;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.ImportsContextCustomizerFactoryIntegrationTests.ImportedBean;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Import;
import org.springframework.stereotype.Component;
import org.springframework.test.context.junit4.SpringRunner;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Integration tests for {@link ImportsContextCustomizerFactory} and
* {@link ImportsContextCustomizer}.
*
* @author Phillip Webb
*/
@RunWith(SpringRunner.class)
@Import(ImportedBean.class)
public class ImportsContextCustomizerFactoryIntegrationTests {
@Autowired
private ApplicationContext context;
@Autowired
private ImportedBean bean;
@Test
public void beanWasImported() throws Exception {
assertThat(this.bean).isNotNull();
}
@Test(expected = NoSuchBeanDefinitionException.class)
public void testItselfIsNotABean() throws Exception {
this.context.getBean(getClass());
}
@Component
static class ImportedBean {
}
}

View File

@@ -0,0 +1,175 @@
/*
* Copyright 2012-2016 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.test.context;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Import;
import org.springframework.stereotype.Component;
import org.springframework.test.context.ContextCustomizer;
import org.springframework.test.context.MergedContextConfiguration;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
/**
* Tests for {@link ImportsContextCustomizerFactory} and {@link ImportsContextCustomizer}.
*
* @author Phillip Webb
* @author Andy Wilkinson
*/
public class ImportsContextCustomizerFactoryTests {
@Rule
public ExpectedException thrown = ExpectedException.none();
private ImportsContextCustomizerFactory factory = new ImportsContextCustomizerFactory();
@Test
public void getContextCustomizerWhenHasNoImportAnnotationShouldReturnNull() {
ContextCustomizer customizer = this.factory
.createContextCustomizer(TestWithNoImport.class, null);
assertThat(customizer).isNull();
}
@Test
public void getContextCustomizerWhenHasImportAnnotationShouldReturnCustomizer() {
ContextCustomizer customizer = this.factory
.createContextCustomizer(TestWithImport.class, null);
assertThat(customizer).isNotNull();
}
@Test
public void getContextCustomizerWhenHasMetaImportAnnotationShouldReturnCustomizer() {
ContextCustomizer customizer = this.factory
.createContextCustomizer(TestWithMetaImport.class, null);
assertThat(customizer).isNotNull();
}
@Test
public void contextCustomizerEqualsAndHashCode() throws Exception {
ContextCustomizer customizer1 = this.factory
.createContextCustomizer(TestWithImport.class, null);
ContextCustomizer customizer2 = this.factory
.createContextCustomizer(TestWithImport.class, null);
ContextCustomizer customizer3 = this.factory
.createContextCustomizer(TestWithImportAndMetaImport.class, null);
ContextCustomizer customizer4 = this.factory
.createContextCustomizer(TestWithSameImportAndMetaImport.class, null);
assertThat(customizer1.hashCode()).isEqualTo(customizer1.hashCode());
assertThat(customizer1.hashCode()).isEqualTo(customizer2.hashCode());
assertThat(customizer1).isEqualTo(customizer1).isEqualTo(customizer2)
.isNotEqualTo(customizer3);
assertThat(customizer3).isEqualTo(customizer4);
}
@Test
public void getContextCustomizerWhenClassHasBeanMethodsShouldThrowException()
throws Exception {
this.thrown.expect(IllegalStateException.class);
this.thrown.expectMessage("Test classes cannot include @Bean methods");
this.factory.createContextCustomizer(TestWithImportAndBeanMethod.class, null);
}
@Test
public void contextCustomizerImportsBeans() throws Exception {
ContextCustomizer customizer = this.factory
.createContextCustomizer(TestWithImport.class, null);
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
customizer.customizeContext(context, mock(MergedContextConfiguration.class));
context.refresh();
assertThat(context.getBean(ImportedBean.class)).isNotNull();
}
@Test
public void selfAnnotatingAnnotationDoesNotCauseStackOverflow() {
assertThat(this.factory.createContextCustomizer(
TestWithImportAndSelfAnnotatingAnnotation.class, null)).isNotNull();
}
static class TestWithNoImport {
}
@Import(ImportedBean.class)
static class TestWithImport {
}
@MetaImport
static class TestWithMetaImport {
}
@MetaImport
@Import(AnotherImportedBean.class)
static class TestWithImportAndMetaImport {
}
@MetaImport
@Import(AnotherImportedBean.class)
static class TestWithSameImportAndMetaImport {
}
@Import(ImportedBean.class)
static class TestWithImportAndBeanMethod {
@Bean
public String bean() {
return "bean";
}
}
@SelfAnnotating
@Import(ImportedBean.class)
static class TestWithImportAndSelfAnnotatingAnnotation {
}
@Retention(RetentionPolicy.RUNTIME)
@Import(ImportedBean.class)
@interface MetaImport {
}
@Component
static class ImportedBean {
}
@Component
static class AnotherImportedBean {
}
@Retention(RetentionPolicy.RUNTIME)
@SelfAnnotating
static @interface SelfAnnotating {
}
}

View File

@@ -0,0 +1,175 @@
/*
* 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.test.context;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.Collections;
import java.util.Set;
import kotlin.Metadata;
import org.junit.Test;
import org.spockframework.runtime.model.SpecMetadata;
import spock.lang.Issue;
import spock.lang.Stepwise;
import org.springframework.boot.context.annotation.DeterminableImports;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.context.annotation.ImportSelector;
import org.springframework.core.type.AnnotationMetadata;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link ImportsContextCustomizer}.
*
* @author Andy Wilkinson
*/
public class ImportsContextCustomizerTests {
@Test
public void importSelectorsCouldUseAnyAnnotations() throws Exception {
assertThat(new ImportsContextCustomizer(FirstImportSelectorAnnotatedClass.class))
.isNotEqualTo(new ImportsContextCustomizer(
SecondImportSelectorAnnotatedClass.class));
}
@Test
public void determinableImportSelector() throws Exception {
assertThat(new ImportsContextCustomizer(
FirstDeterminableImportSelectorAnnotatedClass.class))
.isEqualTo(new ImportsContextCustomizer(
SecondDeterminableImportSelectorAnnotatedClass.class));
}
@Test
public void customizersForTestClassesWithDifferentKotlinMetadataAreEqual() {
assertThat(new ImportsContextCustomizer(FirstKotlinAnnotatedTestClass.class))
.isEqualTo(new ImportsContextCustomizer(
SecondKotlinAnnotatedTestClass.class));
}
@Test
public void customizersForTestClassesWithDifferentSpockFrameworkAnnotationsAreEqual() {
assertThat(
new ImportsContextCustomizer(FirstSpockFrameworkAnnotatedTestClass.class))
.isEqualTo(new ImportsContextCustomizer(
SecondSpockFrameworkAnnotatedTestClass.class));
}
@Test
public void customizersForTestClassesWithDifferentSpockLangAnnotationsAreEqual() {
assertThat(new ImportsContextCustomizer(FirstSpockLangAnnotatedTestClass.class))
.isEqualTo(new ImportsContextCustomizer(
SecondSpockLangAnnotatedTestClass.class));
}
@Import(TestImportSelector.class)
@Indicator1
static class FirstImportSelectorAnnotatedClass {
}
@Import(TestImportSelector.class)
@Indicator2
static class SecondImportSelectorAnnotatedClass {
}
@Import(TestDeterminableImportSelector.class)
@Indicator1
static class FirstDeterminableImportSelectorAnnotatedClass {
}
@Import(TestDeterminableImportSelector.class)
@Indicator2
static class SecondDeterminableImportSelectorAnnotatedClass {
}
@Metadata(d2 = "foo")
static class FirstKotlinAnnotatedTestClass {
}
@Metadata(d2 = "bar")
static class SecondKotlinAnnotatedTestClass {
}
@SpecMetadata(filename = "foo", line = 10)
static class FirstSpockFrameworkAnnotatedTestClass {
}
@SpecMetadata(filename = "bar", line = 10)
static class SecondSpockFrameworkAnnotatedTestClass {
}
@Stepwise
static class FirstSpockLangAnnotatedTestClass {
}
@Issue("1234")
static class SecondSpockLangAnnotatedTestClass {
}
@Retention(RetentionPolicy.RUNTIME)
@interface Indicator1 {
}
@Retention(RetentionPolicy.RUNTIME)
@interface Indicator2 {
}
static class TestImportSelector implements ImportSelector {
@Override
public String[] selectImports(AnnotationMetadata arg0) {
return new String[] {};
}
}
static class TestDeterminableImportSelector
implements ImportSelector, DeterminableImports {
@Override
public String[] selectImports(AnnotationMetadata arg0) {
return new String[] { TestConfig.class.getName() };
}
@Override
public Set<Object> determineImports(AnnotationMetadata metadata) {
return Collections.<Object>singleton(TestConfig.class.getName());
}
}
@Configuration
static class TestConfig {
}
}

View File

@@ -0,0 +1,73 @@
/*
* Copyright 2012-2016 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.test.context;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.springframework.boot.test.context.example.ExampleConfig;
import org.springframework.boot.test.context.example.scan.Example;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link SpringBootConfigurationFinder}.
*
* @author Phillip Webb
*/
public class SpringBootConfigurationFinderTests {
@Rule
public ExpectedException thrown = ExpectedException.none();
private SpringBootConfigurationFinder finder = new SpringBootConfigurationFinder();
@Test
public void findFromClassWhenSourceIsNullShouldThrowException() throws Exception {
this.thrown.expect(IllegalArgumentException.class);
this.thrown.expectMessage("Source must not be null");
this.finder.findFromClass((Class<?>) null);
}
@Test
public void findFromPackageWhenSourceIsNullShouldThrowException() throws Exception {
this.thrown.expect(IllegalArgumentException.class);
this.thrown.expectMessage("Source must not be null");
this.finder.findFromPackage((String) null);
}
@Test
public void findFromPackageWhenNoConfigurationFoundShouldReturnNull() {
Class<?> config = this.finder.findFromPackage("org.springframework.boot");
assertThat(config).isNull();
}
@Test
public void findFromClassWhenConfigurationIsFoundShouldReturnConfiguration() {
Class<?> config = this.finder.findFromClass(Example.class);
assertThat(config).isEqualTo(ExampleConfig.class);
}
@Test
public void findFromPackageWhenConfigurationIsFoundShouldReturnConfiguration() {
Class<?> config = this.finder
.findFromPackage("org.springframework.boot.test.context.example.scan");
assertThat(config).isEqualTo(ExampleConfig.class);
}
}

View File

@@ -0,0 +1,92 @@
/*
* Copyright 2012-2016 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.test.context;
import javax.servlet.ServletContext;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.support.WebApplicationContextUtils;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import static org.assertj.core.api.Assertions.assertThat;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
/**
* Tests for {@link WebAppConfiguration} integration.
*
* @author Stephane Nicoll
*/
@RunWith(SpringRunner.class)
@DirtiesContext
@ContextConfiguration(loader = SpringBootContextLoader.class)
@WebAppConfiguration
public class SpringBootContextLoaderMockMvcTests {
@Autowired
private WebApplicationContext context;
@Autowired
private ServletContext servletContext;
private MockMvc mvc;
@Before
public void setUp() {
this.mvc = MockMvcBuilders.webAppContextSetup(this.context).build();
}
@Test
public void testMockHttpEndpoint() throws Exception {
this.mvc.perform(get("/")).andExpect(status().isOk())
.andExpect(content().string("Hello World"));
}
@Test
public void validateWebApplicationContextIsSet() {
assertThat(this.context).isSameAs(
WebApplicationContextUtils.getWebApplicationContext(this.servletContext));
}
@Configuration
@EnableWebMvc
@RestController
protected static class Config {
@RequestMapping("/")
public String home() {
return "Hello World";
}
}
}

View File

@@ -0,0 +1,169 @@
/*
* Copyright 2012-2016 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.test.context;
import java.util.Map;
import org.junit.Ignore;
import org.junit.Test;
import org.springframework.context.annotation.Configuration;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.MergedContextConfiguration;
import org.springframework.test.context.TestContext;
import org.springframework.test.context.TestContextManager;
import org.springframework.test.context.support.TestPropertySourceUtils;
import org.springframework.test.util.ReflectionTestUtils;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link SpringBootContextLoader}
*
* @author Stephane Nicoll
*/
public class SpringBootContextLoaderTests {
@Test
public void environmentPropertiesSimple() throws Exception {
Map<String, Object> config = getEnvironmentProperties(SimpleConfig.class);
assertKey(config, "key", "myValue");
assertKey(config, "anotherKey", "anotherValue");
}
@Test
public void environmentPropertiesSimpleNonAlias() throws Exception {
Map<String, Object> config = getEnvironmentProperties(SimpleConfigNonAlias.class);
assertKey(config, "key", "myValue");
assertKey(config, "anotherKey", "anotherValue");
}
@Test
public void environmentPropertiesOverrideDefaults() throws Exception {
Map<String, Object> config = getEnvironmentProperties(OverrideConfig.class);
assertKey(config, "server.port", "2345");
}
@Test
public void environmentPropertiesAppend() throws Exception {
Map<String, Object> config = getEnvironmentProperties(AppendConfig.class);
assertKey(config, "key", "myValue");
assertKey(config, "otherKey", "otherValue");
}
@Test
public void environmentPropertiesSeparatorInValue() throws Exception {
Map<String, Object> config = getEnvironmentProperties(SameSeparatorInValue.class);
assertKey(config, "key", "my=Value");
assertKey(config, "anotherKey", "another:Value");
}
@Test
public void environmentPropertiesAnotherSeparatorInValue() throws Exception {
Map<String, Object> config = getEnvironmentProperties(
AnotherSeparatorInValue.class);
assertKey(config, "key", "my:Value");
assertKey(config, "anotherKey", "another=Value");
}
@Test
@Ignore
public void environmentPropertiesNewLineInValue() throws Exception {
// gh-4384
Map<String, Object> config = getEnvironmentProperties(NewLineInValue.class);
assertKey(config, "key", "myValue");
assertKey(config, "variables", "foo=FOO\n bar=BAR");
}
private Map<String, Object> getEnvironmentProperties(Class<?> testClass)
throws Exception {
TestContext context = new ExposedTestContextManager(testClass)
.getExposedTestContext();
MergedContextConfiguration config = (MergedContextConfiguration) ReflectionTestUtils
.getField(context, "mergedContextConfiguration");
return TestPropertySourceUtils
.convertInlinedPropertiesToMap(config.getPropertySourceProperties());
}
private void assertKey(Map<String, Object> actual, String key, Object value) {
assertThat(actual.containsKey(key)).as("Key '" + key + "' not found").isTrue();
assertThat(actual.get(key)).isEqualTo(value);
}
@SpringBootTest({ "key=myValue", "anotherKey:anotherValue" })
@ContextConfiguration(classes = Config.class)
static class SimpleConfig {
}
@SpringBootTest(properties = { "key=myValue", "anotherKey:anotherValue" })
@ContextConfiguration(classes = Config.class)
static class SimpleConfigNonAlias {
}
@SpringBootTest("server.port=2345")
@ContextConfiguration(classes = Config.class)
static class OverrideConfig {
}
@SpringBootTest({ "key=myValue", "otherKey=otherValue" })
@ContextConfiguration(classes = Config.class)
static class AppendConfig {
}
@SpringBootTest({ "key=my=Value", "anotherKey:another:Value" })
@ContextConfiguration(classes = Config.class)
static class SameSeparatorInValue {
}
@SpringBootTest({ "key=my:Value", "anotherKey:another=Value" })
@ContextConfiguration(classes = Config.class)
static class AnotherSeparatorInValue {
}
@SpringBootTest({ "key=myValue", "variables=foo=FOO\n bar=BAR" })
@ContextConfiguration(classes = Config.class)
static class NewLineInValue {
}
@Configuration
static class Config {
}
/**
* {@link TestContextManager} which exposes the {@link TestContext}.
*/
private static class ExposedTestContextManager extends TestContextManager {
ExposedTestContextManager(Class<?> testClass) {
super(testClass);
}
public final TestContext getExposedTestContext() {
return super.getTestContext();
}
}
}

View File

@@ -0,0 +1,56 @@
/*
* Copyright 2012-2016 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.test.context;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Configuration;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.junit4.SpringRunner;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link SpringBootTest} with active profiles. See gh-1469.
*
* @author Phillip Webb
*/
@RunWith(SpringRunner.class)
@DirtiesContext
@SpringBootTest("spring.config.name=enableother")
@ActiveProfiles("override")
public class SpringBootTestActiveProfileTests {
@Autowired
private ApplicationContext context;
@Test
public void profiles() throws Exception {
assertThat(this.context.getEnvironment().getActiveProfiles())
.containsExactly("override");
}
@Configuration
protected static class Config {
}
}

View File

@@ -0,0 +1,69 @@
/*
* 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.test.context;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTestContextHierarchyTests.ChildConfiguration;
import org.springframework.boot.test.context.SpringBootTestContextHierarchyTests.ParentConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.ContextHierarchy;
import org.springframework.test.context.junit4.SpringRunner;
/**
* Tests for {@link SpringBootTest} and {@link ContextHierarchy}.
*
* @author Andy Wilkinson
*/
@SpringBootTest
@ContextHierarchy({ @ContextConfiguration(classes = ParentConfiguration.class),
@ContextConfiguration(classes = ChildConfiguration.class) })
@RunWith(SpringRunner.class)
public class SpringBootTestContextHierarchyTests {
@Test
public void contextLoads() {
}
@Configuration
static class ParentConfiguration {
@Bean
MyBean myBean() {
return new MyBean();
}
}
@Configuration
static class ChildConfiguration {
ChildConfiguration(MyBean myBean) {
}
}
static class MyBean {
}
}

View File

@@ -0,0 +1,55 @@
/*
* Copyright 2012-2016 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.test.context;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.support.PropertySourcesPlaceholderConfigurer;
import org.springframework.test.context.junit4.SpringRunner;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@SpringBootTest} with a custom config name
*
* @author Andy Wilkinson
*/
@RunWith(SpringRunner.class)
@SpringBootTest(properties = "spring.config.name=custom-config-name")
public class SpringBootTestCustomConfigNameTests {
@Value("${test.foo}")
private String foo;
@Test
public void propertyIsLoadedFromConfigFileWithCustomName() {
assertThat(this.foo).isEqualTo("bar");
}
@Configuration
static class TestConfiguration {
public PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() {
return new PropertySourcesPlaceholderConfigurer();
}
}
}

View File

@@ -0,0 +1,53 @@
/*
* Copyright 2012-2016 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.test.context;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.Environment;
import org.springframework.test.context.junit4.SpringRunner;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Test for {@link SpringBootTest} with a custom inline server.port in a non-embedded web
* environment.
*
* @author Stephane Nicoll
*/
@RunWith(SpringRunner.class)
@SpringBootTest(properties = "server.port=12345")
public class SpringBootTestCustomPortTests {
@Autowired
private Environment environment;
@Test
public void validatePortIsNotOverwritten() {
String port = this.environment.getProperty("server.port");
assertThat(port).isEqualTo("12345");
}
@Configuration
protected static class Config {
}
}

View File

@@ -0,0 +1,51 @@
/*
* Copyright 2012-2016 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.test.context;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.junit4.SpringRunner;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link SpringBootTest} (detectDefaultConfigurationClasses).
*
* @author Dave Syer
*/
@RunWith(SpringRunner.class)
@DirtiesContext
public class SpringBootTestDefaultConfigurationTests {
@Autowired
private Config config;
@Test
public void nestedConfigClasses() {
assertThat(this.config).isNotNull();
}
@Configuration
protected static class Config {
}
}

View File

@@ -0,0 +1,48 @@
/*
* Copyright 2012-2016 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.test.context;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringRunner;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link SpringBootTest} (detectDefaultConfigurationClasses).
*
* @author Dave Syer
*/
@RunWith(SpringRunner.class)
@DirtiesContext
@SpringBootTest
@ContextConfiguration(locations = "classpath:test.groovy")
public class SpringBootTestGroovyConfigurationTests {
@Autowired
private String foo;
@Test
public void groovyConfigLoaded() {
assertThat(this.foo).isNotNull();
}
}

View File

@@ -0,0 +1,46 @@
/*
* Copyright 2012-2016 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.test.context;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.junit4.SpringRunner;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link SpringBootTest} finding groovy config.
*
* @author Phillip Webb
*/
@RunWith(SpringRunner.class)
@SpringBootTest
@DirtiesContext
public class SpringBootTestGroovyConventionConfigurationTests {
@Autowired
private String foo;
@Test
public void groovyConfigLoaded() {
assertThat(this.foo).isEqualTo("World");
}
}

Some files were not shown because too many files have changed in this diff Show More