Inherit enclosing class's configuration in nested tests

Fixes gh-12470
This commit is contained in:
Andy Wilkinson 2020-10-28 07:03:39 +00:00
parent b0a1c2a740
commit 6b437ece54
11 changed files with 171 additions and 48 deletions

View File

@ -1,5 +1,5 @@
/*
* Copyright 2012-2019 the original author or authors.
* Copyright 2012-2020 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.
@ -21,12 +21,12 @@ import java.util.List;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.test.util.TestPropertyValues;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.annotation.MergedAnnotations;
import org.springframework.core.annotation.MergedAnnotations.SearchStrategy;
import org.springframework.test.context.ContextConfigurationAttributes;
import org.springframework.test.context.ContextCustomizer;
import org.springframework.test.context.ContextCustomizerFactory;
import org.springframework.test.context.MergedContextConfiguration;
import org.springframework.test.context.TestContextAnnotationUtils;
import org.springframework.test.context.TestContextAnnotationUtils.AnnotationDescriptor;
/**
* {@link ContextCustomizerFactory} to support
@ -39,8 +39,9 @@ class OverrideAutoConfigurationContextCustomizerFactory implements ContextCustom
@Override
public ContextCustomizer createContextCustomizer(Class<?> testClass,
List<ContextConfigurationAttributes> configurationAttributes) {
boolean enabled = MergedAnnotations.from(testClass, SearchStrategy.TYPE_HIERARCHY)
.get(OverrideAutoConfiguration.class).getValue("enabled", Boolean.class).orElse(true);
AnnotationDescriptor<OverrideAutoConfiguration> descriptor = TestContextAnnotationUtils
.findAnnotationDescriptor(testClass, OverrideAutoConfiguration.class);
boolean enabled = (descriptor != null) ? descriptor.getAnnotation().enabled() : true;
return !enabled ? new DisableAutoConfigurationContextCustomizer() : null;
}

View File

@ -20,12 +20,11 @@ import java.util.List;
import org.springframework.boot.test.util.TestPropertyValues;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.annotation.MergedAnnotations;
import org.springframework.core.annotation.MergedAnnotations.SearchStrategy;
import org.springframework.test.context.ContextConfigurationAttributes;
import org.springframework.test.context.ContextCustomizer;
import org.springframework.test.context.ContextCustomizerFactory;
import org.springframework.test.context.MergedContextConfiguration;
import org.springframework.test.context.TestContextAnnotationUtils;
/**
* {@link ContextCustomizerFactory} that globally disables metrics export unless
@ -38,8 +37,8 @@ class MetricsExportContextCustomizerFactory implements ContextCustomizerFactory
@Override
public ContextCustomizer createContextCustomizer(Class<?> testClass,
List<ContextConfigurationAttributes> configAttributes) {
boolean disableMetricsExport = !MergedAnnotations.from(testClass, SearchStrategy.TYPE_HIERARCHY)
.get(AutoConfigureMetrics.class).isPresent();
boolean disableMetricsExport = TestContextAnnotationUtils.findAnnotationDescriptor(testClass,
AutoConfigureMetrics.class) == null;
return disableMetricsExport ? new DisableMetricExportContextCustomizer() : null;
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2012-2019 the original author or authors.
* Copyright 2012-2020 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.
@ -22,10 +22,11 @@ import java.util.List;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Import;
import org.springframework.core.annotation.MergedAnnotations;
import org.springframework.core.annotation.MergedAnnotations.SearchStrategy;
import org.springframework.test.context.ContextConfigurationAttributes;
import org.springframework.test.context.ContextCustomizer;
import org.springframework.test.context.ContextCustomizerFactory;
import org.springframework.test.context.TestContextAnnotationUtils;
import org.springframework.test.context.TestContextAnnotationUtils.AnnotationDescriptor;
import org.springframework.util.Assert;
import org.springframework.util.ReflectionUtils;
@ -41,9 +42,11 @@ class ImportsContextCustomizerFactory implements ContextCustomizerFactory {
@Override
public ContextCustomizer createContextCustomizer(Class<?> testClass,
List<ContextConfigurationAttributes> configAttributes) {
if (MergedAnnotations.from(testClass, SearchStrategy.TYPE_HIERARCHY).isPresent(Import.class)) {
assertHasNoBeanMethods(testClass);
return new ImportsContextCustomizer(testClass);
AnnotationDescriptor<Import> descriptor = TestContextAnnotationUtils.findAnnotationDescriptor(testClass,
Import.class);
if (descriptor != null) {
assertHasNoBeanMethods(descriptor.getRootDeclaringClass());
return new ImportsContextCustomizer(descriptor.getRootDeclaringClass());
}
return null;
}

View File

@ -46,6 +46,8 @@ 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.TestContextAnnotationUtils;
import org.springframework.test.context.TestContextAnnotationUtils.AnnotationDescriptor;
import org.springframework.test.context.TestContextBootstrapper;
import org.springframework.test.context.TestExecutionListener;
import org.springframework.test.context.support.DefaultTestContextBootstrapper;
@ -318,8 +320,12 @@ public class SpringBootTestContextBootstrapper extends DefaultTestContextBootstr
}
protected SpringBootTest getAnnotation(Class<?> testClass) {
return MergedAnnotations.from(testClass, SearchStrategy.INHERITED_ANNOTATIONS).get(SpringBootTest.class)
.synthesize(MergedAnnotation::isPresent).orElse(null);
AnnotationDescriptor<SpringBootTest> descriptor = TestContextAnnotationUtils.findAnnotationDescriptor(testClass,
SpringBootTest.class);
if (descriptor != null) {
return descriptor.getAnnotation();
}
return null;
}
protected void verifyConfiguration(Class<?> testClass) {

View File

@ -18,9 +18,10 @@ package org.springframework.boot.test.context;
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.annotation.MergedAnnotations;
import org.springframework.test.context.ContextCustomizer;
import org.springframework.test.context.MergedContextConfiguration;
import org.springframework.test.context.TestContextAnnotationUtils;
import org.springframework.test.context.TestContextAnnotationUtils.AnnotationDescriptor;
/**
* {@link ContextCustomizer} to track the web environment that is used in a
@ -35,8 +36,9 @@ class SpringBootTestWebEnvironment implements ContextCustomizer {
private final WebEnvironment webEnvironment;
SpringBootTestWebEnvironment(Class<?> testClass) {
this.webEnvironment = MergedAnnotations.from(testClass, MergedAnnotations.SearchStrategy.TYPE_HIERARCHY)
.get(SpringBootTest.class).getValue("webEnvironment", WebEnvironment.class).orElse(null);
AnnotationDescriptor<SpringBootTest> descriptor = TestContextAnnotationUtils.findAnnotationDescriptor(testClass,
SpringBootTest.class);
this.webEnvironment = (descriptor != null) ? descriptor.getAnnotation().webEnvironment() : null;
}
@Override

View File

@ -1,5 +1,5 @@
/*
* Copyright 2012-2019 the original author or authors.
* Copyright 2012-2020 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.
@ -21,6 +21,7 @@ import java.util.List;
import org.springframework.test.context.ContextConfigurationAttributes;
import org.springframework.test.context.ContextCustomizer;
import org.springframework.test.context.ContextCustomizerFactory;
import org.springframework.test.context.TestContextAnnotationUtils;
/**
* A {@link ContextCustomizerFactory} to add Mockito support.
@ -35,8 +36,15 @@ class MockitoContextCustomizerFactory implements ContextCustomizerFactory {
// 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);
parseDefinitions(testClass, parser);
return new MockitoContextCustomizer(parser.getDefinitions());
}
private void parseDefinitions(Class<?> testClass, DefinitionsParser parser) {
parser.parse(testClass);
if (TestContextAnnotationUtils.searchEnclosingClass(testClass)) {
parseDefinitions(testClass.getEnclosingClass(), parser);
}
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2012-2019 the original author or authors.
* Copyright 2012-2020 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.
@ -29,7 +29,6 @@ import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
import org.springframework.boot.test.web.client.TestRestTemplate.HttpClientOption;
import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.boot.web.servlet.server.AbstractServletWebServerFactory;
@ -38,11 +37,10 @@ import org.springframework.context.ApplicationContextAware;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.ConfigurationClassPostProcessor;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.MergedAnnotation;
import org.springframework.core.annotation.MergedAnnotations;
import org.springframework.core.annotation.MergedAnnotations.SearchStrategy;
import org.springframework.test.context.ContextCustomizer;
import org.springframework.test.context.MergedContextConfiguration;
import org.springframework.test.context.TestContextAnnotationUtils;
import org.springframework.test.context.TestContextAnnotationUtils.AnnotationDescriptor;
/**
* {@link ContextCustomizer} for {@link TestRestTemplate}.
@ -55,10 +53,9 @@ class TestRestTemplateContextCustomizer implements ContextCustomizer {
@Override
public void customizeContext(ConfigurableApplicationContext context,
MergedContextConfiguration mergedContextConfiguration) {
MergedAnnotation<?> annotation = MergedAnnotations
.from(mergedContextConfiguration.getTestClass(), SearchStrategy.INHERITED_ANNOTATIONS)
.get(SpringBootTest.class);
if (annotation.getEnum("webEnvironment", WebEnvironment.class).isEmbedded()) {
AnnotationDescriptor<SpringBootTest> springBootTest = TestContextAnnotationUtils
.findAnnotationDescriptor(mergedContextConfiguration.getTestClass(), SpringBootTest.class);
if (springBootTest.getAnnotation().webEnvironment().isEmbedded()) {
registerTestRestTemplate(context);
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2012-2019 the original author or authors.
* Copyright 2012-2020 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.
@ -19,11 +19,11 @@ package org.springframework.boot.test.web.client;
import java.util.List;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.core.annotation.MergedAnnotations;
import org.springframework.core.annotation.MergedAnnotations.SearchStrategy;
import org.springframework.test.context.ContextConfigurationAttributes;
import org.springframework.test.context.ContextCustomizer;
import org.springframework.test.context.ContextCustomizerFactory;
import org.springframework.test.context.TestContextAnnotationUtils;
import org.springframework.test.context.TestContextAnnotationUtils.AnnotationDescriptor;
/**
* {@link ContextCustomizerFactory} for {@link TestRestTemplate}.
@ -36,8 +36,9 @@ class TestRestTemplateContextCustomizerFactory implements ContextCustomizerFacto
@Override
public ContextCustomizer createContextCustomizer(Class<?> testClass,
List<ContextConfigurationAttributes> configAttributes) {
MergedAnnotations annotations = MergedAnnotations.from(testClass, SearchStrategy.INHERITED_ANNOTATIONS);
if (annotations.isPresent(SpringBootTest.class)) {
AnnotationDescriptor<SpringBootTest> springBootTest = TestContextAnnotationUtils
.findAnnotationDescriptor(testClass, SpringBootTest.class);
if (springBootTest != null) {
return new TestRestTemplateContextCustomizer();
}
return null;

View File

@ -1,5 +1,5 @@
/*
* Copyright 2012-2019 the original author or authors.
* Copyright 2012-2020 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.
@ -31,7 +31,6 @@ import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
import org.springframework.boot.web.codec.CodecCustomizer;
import org.springframework.boot.web.reactive.server.AbstractReactiveWebServerFactory;
import org.springframework.context.ApplicationContext;
@ -39,11 +38,10 @@ import org.springframework.context.ApplicationContextAware;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.ConfigurationClassPostProcessor;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.MergedAnnotation;
import org.springframework.core.annotation.MergedAnnotations;
import org.springframework.core.annotation.MergedAnnotations.SearchStrategy;
import org.springframework.test.context.ContextCustomizer;
import org.springframework.test.context.MergedContextConfiguration;
import org.springframework.test.context.TestContextAnnotationUtils;
import org.springframework.test.context.TestContextAnnotationUtils.AnnotationDescriptor;
import org.springframework.test.web.reactive.server.WebTestClient;
import org.springframework.util.CollectionUtils;
import org.springframework.web.reactive.function.client.ExchangeStrategies;
@ -57,9 +55,9 @@ class WebTestClientContextCustomizer implements ContextCustomizer {
@Override
public void customizeContext(ConfigurableApplicationContext context, MergedContextConfiguration mergedConfig) {
MergedAnnotation<?> annotation = MergedAnnotations
.from(mergedConfig.getTestClass(), SearchStrategy.INHERITED_ANNOTATIONS).get(SpringBootTest.class);
if (annotation.getEnum("webEnvironment", WebEnvironment.class).isEmbedded()) {
AnnotationDescriptor<SpringBootTest> springBootTest = TestContextAnnotationUtils
.findAnnotationDescriptor(mergedConfig.getTestClass(), SpringBootTest.class);
if (springBootTest.getAnnotation().webEnvironment().isEmbedded()) {
registerWebTestClient(context);
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2012-2019 the original author or authors.
* Copyright 2012-2020 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.
@ -19,11 +19,11 @@ package org.springframework.boot.test.web.reactive.server;
import java.util.List;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.core.annotation.MergedAnnotations;
import org.springframework.core.annotation.MergedAnnotations.SearchStrategy;
import org.springframework.test.context.ContextConfigurationAttributes;
import org.springframework.test.context.ContextCustomizer;
import org.springframework.test.context.ContextCustomizerFactory;
import org.springframework.test.context.TestContextAnnotationUtils;
import org.springframework.test.context.TestContextAnnotationUtils.AnnotationDescriptor;
import org.springframework.util.ClassUtils;
/**
@ -38,8 +38,9 @@ class WebTestClientContextCustomizerFactory implements ContextCustomizerFactory
@Override
public ContextCustomizer createContextCustomizer(Class<?> testClass,
List<ContextConfigurationAttributes> configAttributes) {
MergedAnnotations annotations = MergedAnnotations.from(testClass, SearchStrategy.INHERITED_ANNOTATIONS);
if (isWebClientPresent() && annotations.isPresent(SpringBootTest.class)) {
AnnotationDescriptor<SpringBootTest> springBootTest = TestContextAnnotationUtils
.findAnnotationDescriptor(testClass, SpringBootTest.class);
if (springBootTest != null) {
return new WebTestClientContextCustomizer();
}
return null;

View File

@ -0,0 +1,107 @@
/*
* Copyright 2012-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.test.context.nestedtests;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringBootConfiguration;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.context.nestedtests.InheritedNestedTestConfigurationTests.ActionPerformer;
import org.springframework.boot.test.context.nestedtests.InheritedNestedTestConfigurationTests.AppConfiguration;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.context.annotation.Import;
import org.springframework.stereotype.Component;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
/**
* Tests for nested test configuration when the configuration is inherited from the
* enclosing class (the default behaviour).
*
* @author Andy Wilkinson
*/
@SpringBootTest(classes = AppConfiguration.class)
@Import(ActionPerformer.class)
class InheritedNestedTestConfigurationTests {
@MockBean
Action action;
@Autowired
ActionPerformer performer;
@Test
void mockWasInvokedOnce() {
this.performer.run();
verify(this.action, times(1)).perform();
}
@Test
void mockWasInvokedTwice() {
this.performer.run();
this.performer.run();
verify(this.action, times(2)).perform();
}
@Nested
class InnerTests {
@Test
void mockWasInvokedOnce() {
InheritedNestedTestConfigurationTests.this.performer.run();
verify(InheritedNestedTestConfigurationTests.this.action, times(1)).perform();
}
@Test
void mockWasInvokedTwice() {
InheritedNestedTestConfigurationTests.this.performer.run();
InheritedNestedTestConfigurationTests.this.performer.run();
verify(InheritedNestedTestConfigurationTests.this.action, times(2)).perform();
}
}
@Component
static class ActionPerformer {
private final Action action;
ActionPerformer(Action action) {
this.action = action;
}
void run() {
this.action.perform();
}
}
public interface Action {
void perform();
}
@SpringBootConfiguration
static class AppConfiguration {
}
}