Skip to content

Commit 7c9f2fb

Browse files
committed
spring-projectsGH-3679: Better caching for SpringIntegrationTest
Fixes spring-projects#3679 The `@SpringIntegrationTest` makes a test cache key based on its attributes values when the same application context can be used in different test classes with different endpoints to have stopped originally. * Rework an `IntegrationEndpointsInitializer` to the `SpringIntegrationTestExecutionListener` which consult a `MockIntegrationContext` for endpoints to be started or not before the test execution and definitely stopped after the test execution to have a flexibility with the cached context * Improve a `MockIntegrationContext` to gather `AbstractEndpoint` beans and have them marked for stopping in the beginning of the application context. The `SpringIntegrationTestExecutionListener` takes care about startup in its `beforeTestClass()` * Verify different state for the `SpringIntegrationTest` with the `CachedSpringIntegrationTestAnnotationTests`
1 parent 22b74c7 commit 7c9f2fb

File tree

7 files changed

+147
-131
lines changed

7 files changed

+147
-131
lines changed

spring-integration-test/src/main/java/org/springframework/integration/test/context/IntegrationEndpointsInitializer.java

-89
This file was deleted.

spring-integration-test/src/main/java/org/springframework/integration/test/context/MockIntegrationContext.java

+36-3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2017-2021 the original author or authors.
2+
* Copyright 2017-2022 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -16,20 +16,25 @@
1616

1717
package org.springframework.integration.test.context;
1818

19+
import java.util.ArrayList;
1920
import java.util.Arrays;
2021
import java.util.Collection;
2122
import java.util.HashMap;
23+
import java.util.List;
2224
import java.util.Map;
2325

2426
import org.springframework.beans.BeansException;
2527
import org.springframework.beans.DirectFieldAccessor;
2628
import org.springframework.beans.factory.BeanFactory;
2729
import org.springframework.beans.factory.BeanFactoryAware;
30+
import org.springframework.beans.factory.SmartInitializingSingleton;
31+
import org.springframework.beans.factory.config.BeanPostProcessor;
2832
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
2933
import org.springframework.context.Lifecycle;
3034
import org.springframework.context.SmartLifecycle;
3135
import org.springframework.integration.core.MessageProducer;
3236
import org.springframework.integration.core.MessageSource;
37+
import org.springframework.integration.endpoint.AbstractEndpoint;
3338
import org.springframework.integration.endpoint.IntegrationConsumer;
3439
import org.springframework.integration.endpoint.ReactiveStreamsConsumer;
3540
import org.springframework.integration.endpoint.SourcePollingChannelAdapter;
@@ -57,7 +62,7 @@
5762
*
5863
* @see SpringIntegrationTest
5964
*/
60-
public class MockIntegrationContext implements BeanFactoryAware {
65+
public class MockIntegrationContext implements BeanPostProcessor, SmartInitializingSingleton, BeanFactoryAware {
6166

6267
private static final String HANDLER = "handler";
6368

@@ -68,6 +73,8 @@ public class MockIntegrationContext implements BeanFactoryAware {
6873

6974
private final Map<String, Object> beans = new HashMap<>();
7075

76+
private final List<AbstractEndpoint> autoStartupCandidates = new ArrayList<>();
77+
7178
private ConfigurableListableBeanFactory beanFactory;
7279

7380
@Override
@@ -77,9 +84,35 @@ public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
7784
this.beanFactory = (ConfigurableListableBeanFactory) beanFactory;
7885
}
7986

87+
@Override
88+
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
89+
if (bean instanceof AbstractEndpoint endpoint && endpoint.isAutoStartup()) {
90+
addAutoStartupCandidates(endpoint);
91+
}
92+
return bean;
93+
}
94+
95+
private void addAutoStartupCandidates(AbstractEndpoint endpoint) {
96+
endpoint.setAutoStartup(false);
97+
this.autoStartupCandidates.add(endpoint);
98+
}
99+
100+
@Override
101+
public void afterSingletonsInstantiated() {
102+
this.beanFactory.getBeansOfType(AbstractEndpoint.class)
103+
.values()
104+
.stream()
105+
.filter(AbstractEndpoint::isAutoStartup)
106+
.forEach(this::addAutoStartupCandidates);
107+
}
108+
109+
List<AbstractEndpoint> getAutoStartupCandidates() {
110+
return this.autoStartupCandidates;
111+
}
112+
80113
/**
81114
* Reinstate the mocked beans after execution test to their real state.
82-
* Typically is used from JUnit clean up method.
115+
* Typically, this method is used from JUnit clean up methods.
83116
* @param beanNames the bean names to reset.
84117
* If {@code null}, all the mocked beans are reset
85118
*/

spring-integration-test/src/main/java/org/springframework/integration/test/context/MockIntegrationContextCustomizer.java

+4-26
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2017-2019 the original author or authors.
2+
* Copyright 2017-2022 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -16,10 +16,7 @@
1616

1717
package org.springframework.integration.test.context;
1818

19-
import java.beans.Introspector;
20-
2119
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
22-
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
2320
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
2421
import org.springframework.beans.factory.support.RootBeanDefinition;
2522
import org.springframework.context.ConfigurableApplicationContext;
@@ -30,20 +27,14 @@
3027
/**
3128
* The {@link ContextCustomizer} implementation for Spring Integration specific environment.
3229
* <p>
33-
* Registers {@link MockIntegrationContext}, {@link IntegrationEndpointsInitializer} beans.
30+
* Registers {@link MockIntegrationContext} bean.
3431
*
3532
* @author Artem Bilan
3633
*
3734
* @since 5.0
3835
*/
3936
class MockIntegrationContextCustomizer implements ContextCustomizer {
4037

41-
private final SpringIntegrationTest springIntegrationTest;
42-
43-
MockIntegrationContextCustomizer(SpringIntegrationTest springIntegrationTest) {
44-
this.springIntegrationTest = springIntegrationTest;
45-
}
46-
4738
@Override
4839
public void customizeContext(ConfigurableApplicationContext context, MergedContextConfiguration mergedConfig) {
4940
ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
@@ -52,29 +43,16 @@ public void customizeContext(ConfigurableApplicationContext context, MergedConte
5243

5344
registry.registerBeanDefinition(MockIntegrationContext.MOCK_INTEGRATION_CONTEXT_BEAN_NAME,
5445
new RootBeanDefinition(MockIntegrationContext.class));
55-
56-
String endpointsInitializer = Introspector.decapitalize(IntegrationEndpointsInitializer.class.getSimpleName());
57-
58-
registry.registerBeanDefinition(endpointsInitializer,
59-
BeanDefinitionBuilder.genericBeanDefinition(IntegrationEndpointsInitializer.class)
60-
.addConstructorArgValue(this.springIntegrationTest)
61-
.getBeanDefinition());
62-
6346
}
6447

6548
@Override
6649
public int hashCode() {
67-
return this.springIntegrationTest.hashCode();
50+
return MockIntegrationContextCustomizer.class.hashCode();
6851
}
6952

7053
@Override
7154
public boolean equals(Object obj) {
72-
if (obj == null || obj.getClass() != getClass()) {
73-
return false;
74-
}
75-
76-
MockIntegrationContextCustomizer customizer = (MockIntegrationContextCustomizer) obj;
77-
return this.springIntegrationTest.equals(customizer.springIntegrationTest);
55+
return obj != null && obj.getClass() == getClass();
7856
}
7957

8058
}

spring-integration-test/src/main/java/org/springframework/integration/test/context/MockIntegrationContextCustomizerFactory.java

+3-6
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2017-2019 the original author or authors.
2+
* Copyright 2017-2022 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -38,11 +38,8 @@ class MockIntegrationContextCustomizerFactory implements ContextCustomizerFactor
3838
public ContextCustomizer createContextCustomizer(Class<?> testClass,
3939
List<ContextConfigurationAttributes> configAttributes) {
4040

41-
SpringIntegrationTest springIntegrationTest =
42-
AnnotatedElementUtils.findMergedAnnotation(testClass, SpringIntegrationTest.class);
43-
44-
return springIntegrationTest != null
45-
? new MockIntegrationContextCustomizer(springIntegrationTest)
41+
return AnnotatedElementUtils.hasAnnotation(testClass, SpringIntegrationTest.class)
42+
? new MockIntegrationContextCustomizer()
4643
: null;
4744
}
4845

spring-integration-test/src/main/java/org/springframework/integration/test/context/SpringIntegrationTest.java

+10-6
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2017-2019 the original author or authors.
2+
* Copyright 2017-2022 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -23,6 +23,8 @@
2323
import java.lang.annotation.RetentionPolicy;
2424
import java.lang.annotation.Target;
2525

26+
import org.springframework.test.context.TestExecutionListeners;
27+
2628
/**
2729
* Annotation that can be specified on a test class that runs Spring Integration based tests.
2830
* Provides the following features over and above the regular <em>Spring TestContext
@@ -32,15 +34,15 @@
3234
* {@link MockIntegrationContext#MOCK_INTEGRATION_CONTEXT_BEAN_NAME} which can be used
3335
* in tests for mocking and verifying integration flows.
3436
* </li>
35-
* <li>Registers an {@link IntegrationEndpointsInitializer} bean which is used
37+
* <li>Registers an {@link SpringIntegrationTestExecutionListener} which is used
3638
* to customize {@link org.springframework.integration.endpoint.AbstractEndpoint}
37-
* beans with provided options on this annotation.
39+
* beans with provided options on this annotation before/after the test class.
3840
* </li>
3941
* </ul>
4042
* <p>
4143
* The typical usage of this annotation is like:
4244
* <pre class="code">
43-
* &#064;RunWith(SpringRunner.class)
45+
* &#064;SpringJUnitConfig
4446
* &#064;SpringIntegrationTest
4547
* public class MyIntegrationTests {
4648
*
@@ -60,6 +62,8 @@
6062
@Retention(RetentionPolicy.RUNTIME)
6163
@Documented
6264
@Inherited
65+
@TestExecutionListeners(listeners = SpringIntegrationTestExecutionListener.class,
66+
mergeMode = TestExecutionListeners.MergeMode.MERGE_WITH_DEFAULTS)
6367
public @interface SpringIntegrationTest {
6468

6569
/**
@@ -68,9 +72,9 @@
6872
* bean names to mark them as {@code autoStartup = false}
6973
* during context initialization.
7074
* @return the endpoints name patterns to stop during context initialization
71-
* @see IntegrationEndpointsInitializer
75+
* @see SpringIntegrationTestExecutionListener
7276
* @see org.springframework.util.PatternMatchUtils
7377
*/
74-
String[] noAutoStartup() default {};
78+
String[] noAutoStartup() default { };
7579

7680
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
/*
2+
* Copyright 2017-2022 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.integration.test.context;
18+
19+
import java.util.Arrays;
20+
21+
import org.springframework.context.ApplicationContext;
22+
import org.springframework.core.annotation.AnnotatedElementUtils;
23+
import org.springframework.integration.endpoint.AbstractEndpoint;
24+
import org.springframework.test.context.TestContext;
25+
import org.springframework.test.context.TestExecutionListener;
26+
import org.springframework.util.PatternMatchUtils;
27+
28+
/**
29+
* A {@link TestExecutionListener} to customize {@link AbstractEndpoint} beans according
30+
* to the provided options in the {@link SpringIntegrationTest} annotation
31+
* before and after test class phases.
32+
*
33+
* @author Artem Bilan
34+
*
35+
* @since 5.0
36+
*/
37+
class SpringIntegrationTestExecutionListener implements TestExecutionListener {
38+
39+
@Override
40+
public void beforeTestClass(TestContext testContext) {
41+
SpringIntegrationTest springIntegrationTest =
42+
AnnotatedElementUtils.findMergedAnnotation(testContext.getTestClass(), SpringIntegrationTest.class);
43+
44+
String[] patterns = springIntegrationTest.noAutoStartup();
45+
46+
ApplicationContext applicationContext = testContext.getApplicationContext();
47+
MockIntegrationContext mockIntegrationContext = applicationContext.getBean(MockIntegrationContext.class);
48+
mockIntegrationContext.getAutoStartupCandidates()
49+
.stream()
50+
.filter(endpoint -> !match(endpoint.getBeanName(), patterns))
51+
.peek(endpoint -> endpoint.setAutoStartup(true))
52+
.forEach(AbstractEndpoint::start);
53+
}
54+
55+
@Override
56+
public void afterTestClass(TestContext testContext) {
57+
ApplicationContext applicationContext = testContext.getApplicationContext();
58+
MockIntegrationContext mockIntegrationContext = applicationContext.getBean(MockIntegrationContext.class);
59+
mockIntegrationContext.getAutoStartupCandidates()
60+
.forEach(AbstractEndpoint::stop);
61+
}
62+
63+
private boolean match(String name, String[] patterns) {
64+
return Arrays.stream(patterns)
65+
.anyMatch(pattern -> PatternMatchUtils.simpleMatch(pattern, name));
66+
}
67+
68+
}

0 commit comments

Comments
 (0)