diff --git a/spring-boot-project/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/OverrideAutoConfigurationContextCustomizerFactory.java b/spring-boot-project/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/OverrideAutoConfigurationContextCustomizerFactory.java index ab9652cbfbde..887847d77e2a 100644 --- a/spring-boot-project/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/OverrideAutoConfigurationContextCustomizerFactory.java +++ b/spring-boot-project/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/OverrideAutoConfigurationContextCustomizerFactory.java @@ -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 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 configurationAttributes) { - boolean enabled = MergedAnnotations.from(testClass, SearchStrategy.TYPE_HIERARCHY) - .get(OverrideAutoConfiguration.class).getValue("enabled", Boolean.class).orElse(true); + AnnotationDescriptor descriptor = TestContextAnnotationUtils + .findAnnotationDescriptor(testClass, OverrideAutoConfiguration.class); + boolean enabled = (descriptor != null) ? descriptor.getAnnotation().enabled() : true; return !enabled ? new DisableAutoConfigurationContextCustomizer() : null; } diff --git a/spring-boot-project/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/actuate/metrics/MetricsExportContextCustomizerFactory.java b/spring-boot-project/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/actuate/metrics/MetricsExportContextCustomizerFactory.java index 38a0310ed357..1ca75685485a 100644 --- a/spring-boot-project/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/actuate/metrics/MetricsExportContextCustomizerFactory.java +++ b/spring-boot-project/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/actuate/metrics/MetricsExportContextCustomizerFactory.java @@ -20,12 +20,11 @@ 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 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; } diff --git a/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/context/ImportsContextCustomizerFactory.java b/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/context/ImportsContextCustomizerFactory.java index 2cf04e1caae8..11c1889de8ac 100644 --- a/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/context/ImportsContextCustomizerFactory.java +++ b/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/context/ImportsContextCustomizerFactory.java @@ -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 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 configAttributes) { - if (MergedAnnotations.from(testClass, SearchStrategy.TYPE_HIERARCHY).isPresent(Import.class)) { - assertHasNoBeanMethods(testClass); - return new ImportsContextCustomizer(testClass); + AnnotationDescriptor descriptor = TestContextAnnotationUtils.findAnnotationDescriptor(testClass, + Import.class); + if (descriptor != null) { + assertHasNoBeanMethods(descriptor.getRootDeclaringClass()); + return new ImportsContextCustomizer(descriptor.getRootDeclaringClass()); } return null; } diff --git a/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/context/SpringBootTestContextBootstrapper.java b/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/context/SpringBootTestContextBootstrapper.java index 06cf71688331..69e0bd7c3598 100644 --- a/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/context/SpringBootTestContextBootstrapper.java +++ b/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/context/SpringBootTestContextBootstrapper.java @@ -46,6 +46,8 @@ 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 @@ protected String[] getProperties(Class testClass) { } protected SpringBootTest getAnnotation(Class testClass) { - return MergedAnnotations.from(testClass, SearchStrategy.INHERITED_ANNOTATIONS).get(SpringBootTest.class) - .synthesize(MergedAnnotation::isPresent).orElse(null); + AnnotationDescriptor descriptor = TestContextAnnotationUtils.findAnnotationDescriptor(testClass, + SpringBootTest.class); + if (descriptor != null) { + return descriptor.getAnnotation(); + } + return null; } protected void verifyConfiguration(Class testClass) { diff --git a/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/context/SpringBootTestWebEnvironment.java b/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/context/SpringBootTestWebEnvironment.java index 45a431f049ff..286c25ceaeeb 100644 --- a/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/context/SpringBootTestWebEnvironment.java +++ b/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/context/SpringBootTestWebEnvironment.java @@ -18,9 +18,10 @@ 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 descriptor = TestContextAnnotationUtils.findAnnotationDescriptor(testClass, + SpringBootTest.class); + this.webEnvironment = (descriptor != null) ? descriptor.getAnnotation().webEnvironment() : null; } @Override diff --git a/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/mock/mockito/MockitoContextCustomizerFactory.java b/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/mock/mockito/MockitoContextCustomizerFactory.java index e013f82c1eee..2fef24d8b383 100644 --- a/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/mock/mockito/MockitoContextCustomizerFactory.java +++ b/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/mock/mockito/MockitoContextCustomizerFactory.java @@ -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 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 @@ public ContextCustomizer createContextCustomizer(Class testClass, // 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); + } + } + } diff --git a/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/web/client/TestRestTemplateContextCustomizer.java b/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/web/client/TestRestTemplateContextCustomizer.java index 5d7c7b1a0593..015daacad155 100644 --- a/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/web/client/TestRestTemplateContextCustomizer.java +++ b/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/web/client/TestRestTemplateContextCustomizer.java @@ -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.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.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 = TestContextAnnotationUtils + .findAnnotationDescriptor(mergedContextConfiguration.getTestClass(), SpringBootTest.class); + if (springBootTest.getAnnotation().webEnvironment().isEmbedded()) { registerTestRestTemplate(context); } } diff --git a/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/web/client/TestRestTemplateContextCustomizerFactory.java b/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/web/client/TestRestTemplateContextCustomizerFactory.java index 727340237601..c06d6905d9a5 100644 --- a/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/web/client/TestRestTemplateContextCustomizerFactory.java +++ b/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/web/client/TestRestTemplateContextCustomizerFactory.java @@ -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 @@ 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 configAttributes) { - MergedAnnotations annotations = MergedAnnotations.from(testClass, SearchStrategy.INHERITED_ANNOTATIONS); - if (annotations.isPresent(SpringBootTest.class)) { + AnnotationDescriptor springBootTest = TestContextAnnotationUtils + .findAnnotationDescriptor(testClass, SpringBootTest.class); + if (springBootTest != null) { return new TestRestTemplateContextCustomizer(); } return null; diff --git a/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/web/reactive/server/WebTestClientContextCustomizer.java b/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/web/reactive/server/WebTestClientContextCustomizer.java index 40bf66265264..0f5f08214d59 100644 --- a/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/web/reactive/server/WebTestClientContextCustomizer.java +++ b/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/web/reactive/server/WebTestClientContextCustomizer.java @@ -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.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.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 = TestContextAnnotationUtils + .findAnnotationDescriptor(mergedConfig.getTestClass(), SpringBootTest.class); + if (springBootTest.getAnnotation().webEnvironment().isEmbedded()) { registerWebTestClient(context); } } diff --git a/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/web/reactive/server/WebTestClientContextCustomizerFactory.java b/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/web/reactive/server/WebTestClientContextCustomizerFactory.java index 8bb872104a66..45d3ddee21ae 100644 --- a/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/web/reactive/server/WebTestClientContextCustomizerFactory.java +++ b/spring-boot-project/spring-boot-test/src/main/java/org/springframework/boot/test/web/reactive/server/WebTestClientContextCustomizerFactory.java @@ -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 @@ 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 configAttributes) { - MergedAnnotations annotations = MergedAnnotations.from(testClass, SearchStrategy.INHERITED_ANNOTATIONS); - if (isWebClientPresent() && annotations.isPresent(SpringBootTest.class)) { + AnnotationDescriptor springBootTest = TestContextAnnotationUtils + .findAnnotationDescriptor(testClass, SpringBootTest.class); + if (springBootTest != null) { return new WebTestClientContextCustomizer(); } return null; diff --git a/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/context/nestedtests/InheritedNestedTestConfigurationTests.java b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/context/nestedtests/InheritedNestedTestConfigurationTests.java new file mode 100644 index 000000000000..528a8ae355a6 --- /dev/null +++ b/spring-boot-project/spring-boot-test/src/test/java/org/springframework/boot/test/context/nestedtests/InheritedNestedTestConfigurationTests.java @@ -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 { + + } + +}