Skip to content

Commit 5dfffa2

Browse files
committed
GH-216 - Forward an ApplicationContext's ExecutorService to Scenario instances by default.
We now register a default customizer on the Scenario instances created to pick up an ExecutorService defined in the ApplicationContext so that customizations made to that are considered in the test execution.
1 parent fb8f12f commit 5dfffa2

File tree

3 files changed

+61
-2
lines changed

3 files changed

+61
-2
lines changed

spring-modulith-test/src/main/java/org/springframework/modulith/test/ScenarioCustomizer.java

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,17 @@
1616
package org.springframework.modulith.test;
1717

1818
import java.lang.reflect.Method;
19+
import java.util.concurrent.Executor;
20+
import java.util.concurrent.ExecutorService;
1921
import java.util.function.Function;
22+
import java.util.function.Supplier;
2023

2124
import org.awaitility.core.ConditionFactory;
2225
import org.junit.jupiter.api.extension.ExtensionContext;
2326
import org.junit.jupiter.api.extension.InvocationInterceptor;
2427
import org.junit.jupiter.api.extension.ReflectiveInvocationContext;
2528
import org.springframework.context.ApplicationContext;
29+
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
2630
import org.springframework.test.context.junit.jupiter.SpringExtension;
2731
import org.springframework.util.Assert;
2832

@@ -43,6 +47,30 @@ public interface ScenarioCustomizer extends InvocationInterceptor {
4347
*/
4448
Function<ConditionFactory, ConditionFactory> getDefaultCustomizer(Method method, ApplicationContext context);
4549

50+
/**
51+
* Creates a default scenario customizer that will try to find an {@link ExecutorService} in the given
52+
* {@link ApplicationContext} in the following order:
53+
* <ol>
54+
* <li>A unique {@link ExecutorService} bean defined</li>
55+
* <li>A {@link ThreadPoolTaskExecutor} bean defined (the default Spring Boot creates in case no {@link Executor} is
56+
* explicitly defined in the {@link ApplicationContext}</li>
57+
* </ol>
58+
*
59+
* @param context must not be {@literal null}.
60+
* @return will never be {@literal null}.
61+
*/
62+
public static Function<ConditionFactory, ConditionFactory> forwardExecutorService(ApplicationContext context) {
63+
64+
Supplier<ExecutorService> fallback = () -> {
65+
var executor = context.getBeanProvider(ThreadPoolTaskExecutor.class).getIfUnique();
66+
return executor == null ? null : executor.getThreadPoolExecutor();
67+
};
68+
69+
var executorService = context.getBeanProvider(ExecutorService.class).getIfUnique(fallback);
70+
71+
return executorService != null ? it -> it.pollExecutorService(executorService) : Function.identity();
72+
}
73+
4674
/*
4775
* (non-Javadoc)
4876
* @see org.junit.jupiter.api.extension.InvocationInterceptor#interceptTestTemplateMethod(org.junit.jupiter.api.extension.InvocationInterceptor.Invocation, org.junit.jupiter.api.extension.ReflectiveInvocationContext, org.junit.jupiter.api.extension.ExtensionContext)

spring-modulith-test/src/main/java/org/springframework/modulith/test/ScenarioParameterResolver.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,8 @@ public Object resolveParameter(ParameterContext parameterContext, ExtensionConte
7777
var operations = resolveTransactionTemplate(context);
7878
var events = (AssertablePublishedEvents) delegate.resolveParameter(parameterContext, extensionContext);
7979

80-
return new Scenario(operations, context, events);
80+
return new Scenario(operations, context, events)
81+
.setDefaultCustomizer(ScenarioCustomizer.forwardExecutorService(context));
8182
}
8283

8384
private TransactionTemplate resolveTransactionTemplate(ApplicationContext context) {

spring-modulith-test/src/test/java/org/springframework/modulith/test/ScenarioCustomizerIntegrationTests.java

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,17 @@
1919
import static org.mockito.Mockito.*;
2020

2121
import java.lang.reflect.Method;
22+
import java.util.concurrent.ExecutorService;
23+
import java.util.concurrent.Executors;
2224
import java.util.function.Function;
2325

26+
import org.awaitility.Awaitility;
2427
import org.awaitility.core.ConditionFactory;
28+
import org.awaitility.core.ExecutorLifecycle;
2529
import org.junit.jupiter.api.BeforeEach;
2630
import org.junit.jupiter.api.Test;
2731
import org.junit.jupiter.api.extension.ExtendWith;
32+
import org.springframework.beans.factory.annotation.Autowired;
2833
import org.springframework.context.ApplicationContext;
2934
import org.springframework.context.annotation.Bean;
3035
import org.springframework.context.annotation.Configuration;
@@ -50,8 +55,15 @@ static class TestConfiguration {
5055
TransactionTemplate transactionTemplate() {
5156
return mock(TransactionTemplate.class);
5257
}
58+
59+
@Bean
60+
ExecutorService executorService() {
61+
return Executors.newSingleThreadExecutor();
62+
}
5363
}
5464

65+
@Autowired ExecutorService executorService;
66+
5567
@BeforeEach
5668
void setUp() {
5769
TestScenarioCustomizer.invoked = false;
@@ -61,6 +73,8 @@ void setUp() {
6173
void customizerGetsAppliedForScenarioParameter(Scenario scenario) {
6274

6375
assertThat(TestScenarioCustomizer.invoked).isTrue();
76+
assertThat(TestScenarioCustomizer.SAMPLE).isNotNull();
77+
6478
assertThat(ReflectionTestUtils.getField(scenario, "defaultCustomizer"))
6579
.isSameAs(TestScenarioCustomizer.SAMPLE);
6680
}
@@ -70,9 +84,23 @@ void customizerDoesNotGetAppliedForNoScenarioParameter() {
7084
assertThat(TestScenarioCustomizer.invoked).isFalse();
7185
}
7286

87+
@Test // GH-165
88+
@SuppressWarnings("unchecked")
89+
void forwardsExecutorServiceFromApplicationContext(Scenario scenario) {
90+
91+
var customizer = (Function<ConditionFactory, ConditionFactory>) ReflectionTestUtils.getField(scenario,
92+
"defaultCustomizer");
93+
94+
var factory = customizer.apply(Awaitility.await());
95+
var lifecycle = (ExecutorLifecycle) ReflectionTestUtils.getField(factory, "executorLifecycle");
96+
97+
assertThat(lifecycle).isNotNull();
98+
assertThat(lifecycle.supplyExecutorService()).isEqualTo(executorService);
99+
}
100+
73101
static class TestScenarioCustomizer implements ScenarioCustomizer {
74102

75-
static Function<ConditionFactory, ConditionFactory> SAMPLE = it -> it;
103+
static Function<ConditionFactory, ConditionFactory> SAMPLE;
76104
static boolean invoked = false;
77105

78106
@Override
@@ -81,6 +109,8 @@ public Function<ConditionFactory, ConditionFactory> getDefaultCustomizer(Method
81109

82110
invoked = true;
83111

112+
SAMPLE = ScenarioCustomizer.forwardExecutorService(context);
113+
84114
return SAMPLE;
85115
}
86116
}

0 commit comments

Comments
 (0)