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.
This commit is contained in:
@@ -16,13 +16,17 @@
|
||||
package org.springframework.modulith.test;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import org.awaitility.core.ConditionFactory;
|
||||
import org.junit.jupiter.api.extension.ExtensionContext;
|
||||
import org.junit.jupiter.api.extension.InvocationInterceptor;
|
||||
import org.junit.jupiter.api.extension.ReflectiveInvocationContext;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
|
||||
import org.springframework.test.context.junit.jupiter.SpringExtension;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
@@ -43,6 +47,30 @@ public interface ScenarioCustomizer extends InvocationInterceptor {
|
||||
*/
|
||||
Function<ConditionFactory, ConditionFactory> getDefaultCustomizer(Method method, ApplicationContext context);
|
||||
|
||||
/**
|
||||
* Creates a default scenario customizer that will try to find an {@link ExecutorService} in the given
|
||||
* {@link ApplicationContext} in the following order:
|
||||
* <ol>
|
||||
* <li>A unique {@link ExecutorService} bean defined</li>
|
||||
* <li>A {@link ThreadPoolTaskExecutor} bean defined (the default Spring Boot creates in case no {@link Executor} is
|
||||
* explicitly defined in the {@link ApplicationContext}</li>
|
||||
* </ol>
|
||||
*
|
||||
* @param context must not be {@literal null}.
|
||||
* @return will never be {@literal null}.
|
||||
*/
|
||||
public static Function<ConditionFactory, ConditionFactory> forwardExecutorService(ApplicationContext context) {
|
||||
|
||||
Supplier<ExecutorService> fallback = () -> {
|
||||
var executor = context.getBeanProvider(ThreadPoolTaskExecutor.class).getIfUnique();
|
||||
return executor == null ? null : executor.getThreadPoolExecutor();
|
||||
};
|
||||
|
||||
var executorService = context.getBeanProvider(ExecutorService.class).getIfUnique(fallback);
|
||||
|
||||
return executorService != null ? it -> it.pollExecutorService(executorService) : Function.identity();
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @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)
|
||||
|
||||
@@ -77,7 +77,8 @@ class ScenarioParameterResolver implements ParameterResolver, AfterEachCallback
|
||||
var operations = resolveTransactionTemplate(context);
|
||||
var events = (AssertablePublishedEvents) delegate.resolveParameter(parameterContext, extensionContext);
|
||||
|
||||
return new Scenario(operations, context, events);
|
||||
return new Scenario(operations, context, events)
|
||||
.setDefaultCustomizer(ScenarioCustomizer.forwardExecutorService(context));
|
||||
}
|
||||
|
||||
private TransactionTemplate resolveTransactionTemplate(ApplicationContext context) {
|
||||
|
||||
@@ -19,12 +19,17 @@ import static org.assertj.core.api.Assertions.*;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.function.Function;
|
||||
|
||||
import org.awaitility.Awaitility;
|
||||
import org.awaitility.core.ConditionFactory;
|
||||
import org.awaitility.core.ExecutorLifecycle;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
@@ -50,8 +55,15 @@ class ScenarioCustomizerIntegrationTests {
|
||||
TransactionTemplate transactionTemplate() {
|
||||
return mock(TransactionTemplate.class);
|
||||
}
|
||||
|
||||
@Bean
|
||||
ExecutorService executorService() {
|
||||
return Executors.newSingleThreadExecutor();
|
||||
}
|
||||
}
|
||||
|
||||
@Autowired ExecutorService executorService;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
TestScenarioCustomizer.invoked = false;
|
||||
@@ -61,6 +73,8 @@ class ScenarioCustomizerIntegrationTests {
|
||||
void customizerGetsAppliedForScenarioParameter(Scenario scenario) {
|
||||
|
||||
assertThat(TestScenarioCustomizer.invoked).isTrue();
|
||||
assertThat(TestScenarioCustomizer.SAMPLE).isNotNull();
|
||||
|
||||
assertThat(ReflectionTestUtils.getField(scenario, "defaultCustomizer"))
|
||||
.isSameAs(TestScenarioCustomizer.SAMPLE);
|
||||
}
|
||||
@@ -70,9 +84,23 @@ class ScenarioCustomizerIntegrationTests {
|
||||
assertThat(TestScenarioCustomizer.invoked).isFalse();
|
||||
}
|
||||
|
||||
@Test // GH-165
|
||||
@SuppressWarnings("unchecked")
|
||||
void forwardsExecutorServiceFromApplicationContext(Scenario scenario) {
|
||||
|
||||
var customizer = (Function<ConditionFactory, ConditionFactory>) ReflectionTestUtils.getField(scenario,
|
||||
"defaultCustomizer");
|
||||
|
||||
var factory = customizer.apply(Awaitility.await());
|
||||
var lifecycle = (ExecutorLifecycle) ReflectionTestUtils.getField(factory, "executorLifecycle");
|
||||
|
||||
assertThat(lifecycle).isNotNull();
|
||||
assertThat(lifecycle.supplyExecutorService()).isEqualTo(executorService);
|
||||
}
|
||||
|
||||
static class TestScenarioCustomizer implements ScenarioCustomizer {
|
||||
|
||||
static Function<ConditionFactory, ConditionFactory> SAMPLE = it -> it;
|
||||
static Function<ConditionFactory, ConditionFactory> SAMPLE;
|
||||
static boolean invoked = false;
|
||||
|
||||
@Override
|
||||
@@ -81,6 +109,8 @@ class ScenarioCustomizerIntegrationTests {
|
||||
|
||||
invoked = true;
|
||||
|
||||
SAMPLE = ScenarioCustomizer.forwardExecutorService(context);
|
||||
|
||||
return SAMPLE;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user