diff --git a/spring-test/src/main/java/org/springframework/test/context/TestContext.java b/spring-test/src/main/java/org/springframework/test/context/TestContext.java
index 1c1585ff53..59a45a27d9 100644
--- a/spring-test/src/main/java/org/springframework/test/context/TestContext.java
+++ b/spring-test/src/main/java/org/springframework/test/context/TestContext.java
@@ -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.
+ *
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 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})
diff --git a/spring-test/src/main/java/org/springframework/test/context/event/EventPublishingTestExecutionListener.java b/spring-test/src/main/java/org/springframework/test/context/event/EventPublishingTestExecutionListener.java
index 1ac348f0f9..d25af05b3d 100644
--- a/spring-test/src/main/java/org/springframework/test/context/event/EventPublishingTestExecutionListener.java
+++ b/spring-test/src/main/java/org/springframework/test/context/event/EventPublishingTestExecutionListener.java
@@ -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 eventFactory) {
- if (testContext.hasApplicationContext()) {
- testContext.getApplicationContext().publishEvent(eventFactory.apply(testContext));
- }
+ testContext.publishEvent(AfterTestClassEvent::new);
}
}
diff --git a/spring-test/src/test/java/org/springframework/test/context/event/CustomTestEventTests.java b/spring-test/src/test/java/org/springframework/test/context/event/CustomTestEventTests.java
new file mode 100644
index 0000000000..14c3a11032
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/context/event/CustomTestEventTests.java
@@ -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 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()));
+ }
+ }
+
+}
diff --git a/spring-test/src/test/java/org/springframework/test/context/event/EventPublishingTestExecutionListenerIntegrationTests.java b/spring-test/src/test/java/org/springframework/test/context/event/EventPublishingTestExecutionListenerIntegrationTests.java
index 687e99be51..cd7bc64687 100644
--- a/spring-test/src/test/java/org/springframework/test/context/event/EventPublishingTestExecutionListenerIntegrationTests.java
+++ b/spring-test/src/test/java/org/springframework/test/context/event/EventPublishingTestExecutionListenerIntegrationTests.java
@@ -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);
}
diff --git a/spring-test/src/test/java/org/springframework/test/context/event/EventPublishingTestExecutionListenerTests.java b/spring-test/src/test/java/org/springframework/test/context/event/EventPublishingTestExecutionListenerTests.java
index 2ab00c4dce..3f57d5e26c 100644
--- a/spring-test/src/test/java/org/springframework/test/context/event/EventPublishingTestExecutionListenerTests.java
+++ b/spring-test/src/test/java/org/springframework/test/context/event/EventPublishingTestExecutionListenerTests.java
@@ -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 testExecutionEvent;
-
@Rule
public final TestName testName = new TestName();
+ @Mock
+ private TestContext testContext;
+
+ @Mock
+ private ApplicationContext applicationContext;
+
+ @Captor
+ private ArgumentCaptor> 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 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 callback) {
+ private void assertEvent(Class extends TestContextEvent> eventClass, Consumer 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 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));
}
}