Introduce publishEvent() convenience method in TestContext
This commit introduces a publishEvent() method in the TestContext API as a convenience for publishing an ApplicationEvent to the test's ApplicationContext but only if the ApplicationContext is currently available and with lazy creation of the ApplicationEvent. For example, the beforeTestClass() method in EventPublishingTestExecutionListener is now implemented as follows. public void beforeTestClass(TestContext testContext) { testContext.publishEvent(BeforeTestClassEvent::new); } Closes gh-22765
This commit is contained in:
parent
a7425c81c0
commit
cf5d0e6aa9
|
@ -18,8 +18,10 @@ package org.springframework.test.context;
|
|||
|
||||
import java.io.Serializable;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.function.Function;
|
||||
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.ApplicationEvent;
|
||||
import org.springframework.core.AttributeAccessor;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.test.annotation.DirtiesContext.HierarchyMode;
|
||||
|
@ -76,6 +78,23 @@ public interface TestContext extends AttributeAccessor, Serializable {
|
|||
*/
|
||||
ApplicationContext getApplicationContext();
|
||||
|
||||
/**
|
||||
* Publish the {@link ApplicationEvent} created by the given {@code eventFactory}
|
||||
* to the {@linkplain ApplicationContext application context} for this
|
||||
* test context.
|
||||
* <p>The {@code ApplicationEvent} will only be published if the application
|
||||
* context for this test context {@linkplain #hasApplicationContext() is available}.
|
||||
* @param eventFactory factory for lazy creation of the {@code ApplicationEvent}
|
||||
* @since 5.2
|
||||
* @see #hasApplicationContext()
|
||||
* @see #getApplicationContext()
|
||||
*/
|
||||
default void publishEvent(Function<TestContext, ? extends ApplicationEvent> eventFactory) {
|
||||
if (hasApplicationContext()) {
|
||||
getApplicationContext().publishEvent(eventFactory.apply(this));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the {@linkplain Class test class} for this test context.
|
||||
* @return the test class (never {@code null})
|
||||
|
|
|
@ -16,8 +16,6 @@
|
|||
|
||||
package org.springframework.test.context.event;
|
||||
|
||||
import java.util.function.Function;
|
||||
|
||||
import org.springframework.test.context.TestContext;
|
||||
import org.springframework.test.context.TestExecutionListener;
|
||||
import org.springframework.test.context.support.AbstractTestExecutionListener;
|
||||
|
@ -92,7 +90,7 @@ public class EventPublishingTestExecutionListener extends AbstractTestExecutionL
|
|||
*/
|
||||
@Override
|
||||
public void beforeTestClass(TestContext testContext) {
|
||||
publishEvent(testContext, BeforeTestClassEvent::new);
|
||||
testContext.publishEvent(BeforeTestClassEvent::new);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -101,7 +99,7 @@ public class EventPublishingTestExecutionListener extends AbstractTestExecutionL
|
|||
*/
|
||||
@Override
|
||||
public void prepareTestInstance(TestContext testContext) {
|
||||
publishEvent(testContext, PrepareTestInstanceEvent::new);
|
||||
testContext.publishEvent(PrepareTestInstanceEvent::new);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -110,7 +108,7 @@ public class EventPublishingTestExecutionListener extends AbstractTestExecutionL
|
|||
*/
|
||||
@Override
|
||||
public void beforeTestMethod(TestContext testContext) {
|
||||
publishEvent(testContext, BeforeTestMethodEvent::new);
|
||||
testContext.publishEvent(BeforeTestMethodEvent::new);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -119,7 +117,7 @@ public class EventPublishingTestExecutionListener extends AbstractTestExecutionL
|
|||
*/
|
||||
@Override
|
||||
public void beforeTestExecution(TestContext testContext) {
|
||||
publishEvent(testContext, BeforeTestExecutionEvent::new);
|
||||
testContext.publishEvent(BeforeTestExecutionEvent::new);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -128,7 +126,7 @@ public class EventPublishingTestExecutionListener extends AbstractTestExecutionL
|
|||
*/
|
||||
@Override
|
||||
public void afterTestExecution(TestContext testContext) {
|
||||
publishEvent(testContext, AfterTestExecutionEvent::new);
|
||||
testContext.publishEvent(AfterTestExecutionEvent::new);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -137,7 +135,7 @@ public class EventPublishingTestExecutionListener extends AbstractTestExecutionL
|
|||
*/
|
||||
@Override
|
||||
public void afterTestMethod(TestContext testContext) {
|
||||
publishEvent(testContext, AfterTestMethodEvent::new);
|
||||
testContext.publishEvent(AfterTestMethodEvent::new);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -146,13 +144,7 @@ public class EventPublishingTestExecutionListener extends AbstractTestExecutionL
|
|||
*/
|
||||
@Override
|
||||
public void afterTestClass(TestContext testContext) {
|
||||
publishEvent(testContext, AfterTestClassEvent::new);
|
||||
}
|
||||
|
||||
private void publishEvent(TestContext testContext, Function<TestContext, TestContextEvent> eventFactory) {
|
||||
if (testContext.hasApplicationContext()) {
|
||||
testContext.getApplicationContext().publishEvent(eventFactory.apply(testContext));
|
||||
}
|
||||
testContext.publishEvent(AfterTestClassEvent::new);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,100 @@
|
|||
/*
|
||||
* Copyright 2002-2019 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.test.context.event;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import org.springframework.context.ApplicationEvent;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.event.EventListener;
|
||||
import org.springframework.test.context.TestContext;
|
||||
import org.springframework.test.context.TestExecutionListener;
|
||||
import org.springframework.test.context.TestExecutionListeners;
|
||||
import org.springframework.test.context.event.CustomTestEventTests.CustomEventPublishingTestExecutionListener;
|
||||
import org.springframework.test.context.junit4.SpringRunner;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.springframework.test.context.TestExecutionListeners.MergeMode.MERGE_WITH_DEFAULTS;
|
||||
|
||||
/**
|
||||
* Integration tests for custom event publication via
|
||||
* {@link TestContext#publishEvent(java.util.function.Function)}.
|
||||
*
|
||||
* @author Sam Brannen
|
||||
* @since 5.2
|
||||
*/
|
||||
@RunWith(SpringRunner.class)
|
||||
@TestExecutionListeners(listeners = CustomEventPublishingTestExecutionListener.class, mergeMode = MERGE_WITH_DEFAULTS)
|
||||
public class CustomTestEventTests {
|
||||
|
||||
private static final List<CustomEvent> events = new ArrayList<>();
|
||||
|
||||
|
||||
@Before
|
||||
public void clearEvents() {
|
||||
events.clear();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void customTestEventPublished() {
|
||||
assertThat(events).size().isEqualTo(1);
|
||||
CustomEvent customEvent = events.get(0);
|
||||
assertThat(customEvent.getSource()).isEqualTo(getClass());
|
||||
assertThat(customEvent.getTestName()).isEqualTo("customTestEventPublished");
|
||||
}
|
||||
|
||||
|
||||
@Configuration
|
||||
static class Config {
|
||||
|
||||
@EventListener
|
||||
void processCustomEvent(CustomEvent event) {
|
||||
events.add(event);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("serial")
|
||||
static class CustomEvent extends ApplicationEvent {
|
||||
|
||||
private final Method testMethod;
|
||||
|
||||
|
||||
public CustomEvent(Class<?> testClass, Method testMethod) {
|
||||
super(testClass);
|
||||
this.testMethod = testMethod;
|
||||
}
|
||||
|
||||
String getTestName() {
|
||||
return this.testMethod.getName();
|
||||
}
|
||||
}
|
||||
|
||||
static class CustomEventPublishingTestExecutionListener implements TestExecutionListener {
|
||||
|
||||
@Override
|
||||
public void beforeTestExecution(TestContext testContext) throws Exception {
|
||||
testContext.publishEvent(tc -> new CustomEvent(tc.getTestClass(), tc.getTestMethod()));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -77,9 +77,13 @@ public class EventPublishingTestExecutionListenerIntegrationTests {
|
|||
|
||||
private final TestContextManager testContextManager = new TestContextManager(ExampleTestCase.class);
|
||||
private final TestContext testContext = testContextManager.getTestContext();
|
||||
// Note that the following invocation of getApplicationContext() forces eager
|
||||
// loading of the test's ApplicationContext which consequently results in the
|
||||
// publication of all test execution events. Otherwise, TestContext#publishEvent
|
||||
// would never fire any events for ExampleTestCase.
|
||||
private final TestExecutionListener listener = testContext.getApplicationContext().getBean(TestExecutionListener.class);
|
||||
private final Object testInstance = new ExampleTestCase();
|
||||
private final Method testMethod = ReflectionUtils.findMethod(ExampleTestCase.class, "traceableTest");
|
||||
private final Method traceableTestMethod = ReflectionUtils.findMethod(ExampleTestCase.class, "traceableTest");
|
||||
|
||||
@Rule
|
||||
public final ExpectedException exception = ExpectedException.none();
|
||||
|
@ -104,7 +108,7 @@ public class EventPublishingTestExecutionListenerIntegrationTests {
|
|||
|
||||
@Test
|
||||
public void beforeTestMethodAnnotation() throws Exception {
|
||||
testContextManager.beforeTestMethod(testInstance, testMethod);
|
||||
testContextManager.beforeTestMethod(testInstance, traceableTestMethod);
|
||||
verify(listener, only()).beforeTestMethod(testContext);
|
||||
}
|
||||
|
||||
|
@ -162,19 +166,19 @@ public class EventPublishingTestExecutionListenerIntegrationTests {
|
|||
|
||||
@Test
|
||||
public void beforeTestExecutionAnnotation() throws Exception {
|
||||
testContextManager.beforeTestExecution(testInstance, testMethod);
|
||||
testContextManager.beforeTestExecution(testInstance, traceableTestMethod);
|
||||
verify(listener, only()).beforeTestExecution(testContext);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void afterTestExecutionAnnotation() throws Exception {
|
||||
testContextManager.afterTestExecution(testInstance, testMethod, null);
|
||||
testContextManager.afterTestExecution(testInstance, traceableTestMethod, null);
|
||||
verify(listener, only()).afterTestExecution(testContext);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void afterTestMethodAnnotation() throws Exception {
|
||||
testContextManager.afterTestMethod(testInstance, testMethod, null);
|
||||
testContextManager.afterTestMethod(testInstance, traceableTestMethod, null);
|
||||
verify(listener, only()).afterTestMethod(testContext);
|
||||
}
|
||||
|
||||
|
|
|
@ -17,28 +17,27 @@
|
|||
package org.springframework.test.context.event;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.TestName;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Answers;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.Captor;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.MockitoJUnitRunner;
|
||||
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.ApplicationEvent;
|
||||
import org.springframework.test.context.TestContext;
|
||||
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.Matchers.equalTo;
|
||||
import static org.hamcrest.Matchers.instanceOf;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.only;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link EventPublishingTestExecutionListener}.
|
||||
|
@ -52,21 +51,26 @@ public class EventPublishingTestExecutionListenerTests {
|
|||
|
||||
private final EventPublishingTestExecutionListener listener = new EventPublishingTestExecutionListener();
|
||||
|
||||
@Mock(answer = Answers.RETURNS_DEEP_STUBS)
|
||||
private TestContext testContext;
|
||||
|
||||
@Captor
|
||||
private ArgumentCaptor<TestContextEvent> testExecutionEvent;
|
||||
|
||||
@Rule
|
||||
public final TestName testName = new TestName();
|
||||
|
||||
@Mock
|
||||
private TestContext testContext;
|
||||
|
||||
@Mock
|
||||
private ApplicationContext applicationContext;
|
||||
|
||||
@Captor
|
||||
private ArgumentCaptor<Function<TestContext, ? extends ApplicationEvent>> eventFactory;
|
||||
|
||||
|
||||
@Before
|
||||
public void configureMock() {
|
||||
if (testName.getMethodName().startsWith("publish")) {
|
||||
when(testContext.hasApplicationContext()).thenReturn(true);
|
||||
}
|
||||
// Force Mockito to invoke the interface default method
|
||||
doCallRealMethod().when(testContext).publishEvent(any());
|
||||
when(testContext.getApplicationContext()).thenReturn(applicationContext);
|
||||
// Only allow events to be published for test methods named "publish*".
|
||||
when(testContext.hasApplicationContext()).thenReturn(testName.getMethodName().startsWith("publish"));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -104,51 +108,71 @@ public class EventPublishingTestExecutionListenerTests {
|
|||
assertEvent(AfterTestClassEvent.class, listener::afterTestClass);
|
||||
}
|
||||
|
||||
private void assertEvent(Class<? extends TestContextEvent> eventClass, Consumer<TestContext> callback) {
|
||||
callback.accept(testContext);
|
||||
verify(testContext.getApplicationContext(), only()).publishEvent(testExecutionEvent.capture());
|
||||
assertThat(testExecutionEvent.getValue(), instanceOf(eventClass));
|
||||
assertThat(testExecutionEvent.getValue().getSource(), equalTo(testContext));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void doesNotPublishBeforeTestClassEventIfApplicationContextHasNotBeenLoaded() {
|
||||
assertNoEvent(listener::beforeTestClass);
|
||||
assertNoEvent(BeforeTestClassEvent.class, listener::beforeTestClass);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void doesNotPublishPrepareTestInstanceEventIfApplicationContextHasNotBeenLoaded() {
|
||||
assertNoEvent(listener::prepareTestInstance);
|
||||
assertNoEvent(PrepareTestInstanceEvent.class, listener::prepareTestInstance);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void doesNotPublishBeforeTestMethodEventIfApplicationContextHasNotBeenLoaded() {
|
||||
assertNoEvent(listener::beforeTestMethod);
|
||||
assertNoEvent(BeforeTestMethodEvent.class, listener::beforeTestMethod);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void doesNotPublishBeforeTestExecutionEventIfApplicationContextHasNotBeenLoaded() {
|
||||
assertNoEvent(listener::beforeTestExecution);
|
||||
assertNoEvent(BeforeTestExecutionEvent.class, listener::beforeTestExecution);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void doesNotPublishAfterTestExecutionEventIfApplicationContextHasNotBeenLoaded() {
|
||||
assertNoEvent(listener::afterTestExecution);
|
||||
assertNoEvent(AfterTestExecutionEvent.class, listener::afterTestExecution);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void doesNotPublishAfterTestMethodEventIfApplicationContextHasNotBeenLoaded() {
|
||||
assertNoEvent(listener::afterTestMethod);
|
||||
assertNoEvent(AfterTestMethodEvent.class, listener::afterTestMethod);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void doesNotPublishAfterTestClassEventIfApplicationContextHasNotBeenLoaded() {
|
||||
assertNoEvent(listener::afterTestClass);
|
||||
assertNoEvent(AfterTestClassEvent.class, listener::afterTestClass);
|
||||
}
|
||||
|
||||
private void assertNoEvent(Consumer<TestContext> callback) {
|
||||
private void assertEvent(Class<? extends TestContextEvent> eventClass, Consumer<TestContext> callback) {
|
||||
callback.accept(testContext);
|
||||
verify(testContext.getApplicationContext(), never()).publishEvent(any());
|
||||
|
||||
// The listener attempted to publish the event...
|
||||
verify(testContext, times(1)).publishEvent(eventFactory.capture());
|
||||
|
||||
// The listener successfully published the event...
|
||||
verify(applicationContext, times(1)).publishEvent(any());
|
||||
|
||||
// Verify the type of event that was published.
|
||||
ApplicationEvent event = eventFactory.getValue().apply(testContext);
|
||||
assertThat(event, instanceOf(eventClass));
|
||||
assertThat(event.getSource(), equalTo(testContext));
|
||||
}
|
||||
|
||||
private void assertNoEvent(Class<? extends TestContextEvent> eventClass, Consumer<TestContext> callback) {
|
||||
callback.accept(testContext);
|
||||
|
||||
// The listener attempted to publish the event...
|
||||
verify(testContext, times(1)).publishEvent(eventFactory.capture());
|
||||
|
||||
// But the event was not actually published since the ApplicationContext
|
||||
// was not available.
|
||||
verify(applicationContext, never()).publishEvent(any());
|
||||
|
||||
// In any case, we can still verify the type of event that would have
|
||||
// been published.
|
||||
ApplicationEvent event = eventFactory.getValue().apply(testContext);
|
||||
assertThat(event, instanceOf(eventClass));
|
||||
assertThat(event.getSource(), equalTo(testContext));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue