diff --git a/spring-test/src/main/java/org/springframework/test/context/event/TestContextEvent.java b/spring-test/src/main/java/org/springframework/test/context/event/TestContextEvent.java index 7a1bfb0d93..e25c14732c 100644 --- a/spring-test/src/main/java/org/springframework/test/context/event/TestContextEvent.java +++ b/spring-test/src/main/java/org/springframework/test/context/event/TestContextEvent.java @@ -39,12 +39,25 @@ public abstract class TestContextEvent extends ApplicationEvent { } /** - * Get the {@code TestContext} associated with this event. + * Get the {@link TestContext} associated with this event. * @return the {@code TestContext} associated with this event (never {@code null}) + * @see #getTestContext() */ @Override - public TestContext getSource() { + public final TestContext getSource() { return (TestContext) super.getSource(); } + /** + * Alias for {@link #getSource()}. + *
This method may be favored over {@code getSource()} to improve readability + * in SpEL expressions for event processing + * {@linkplain org.springframework.context.event.EventListener#condition conditions}. + * @return the {@code TestContext} associated with this event (never {@code null}) + * @see #getSource() + */ + public final TestContext getTestContext() { + return getSource(); + } + } diff --git a/spring-test/src/main/java/org/springframework/test/context/event/annotation/AfterTestClass.java b/spring-test/src/main/java/org/springframework/test/context/event/annotation/AfterTestClass.java index 349446f487..aa84f3d107 100644 --- a/spring-test/src/main/java/org/springframework/test/context/event/annotation/AfterTestClass.java +++ b/spring-test/src/main/java/org/springframework/test/context/event/annotation/AfterTestClass.java @@ -21,6 +21,7 @@ import java.lang.annotation.Retention; import java.lang.annotation.Target; import org.springframework.context.event.EventListener; +import org.springframework.core.annotation.AliasFor; import org.springframework.test.context.event.AfterTestClassEvent; import static java.lang.annotation.ElementType.ANNOTATION_TYPE; @@ -41,17 +42,29 @@ import static java.lang.annotation.RetentionPolicy.RUNTIME; * {@link org.springframework.test.context.TestExecutionListener#afterTestClass} * lifecycle. * + *
Event processing can optionally be made {@linkplain #value conditional} via + * a SpEL expression — for example, + * {@code @AfterTestClass("event.testContext.testClass.name matches '.+IntegrationTests'")}. + * *
The {@code EventPublishingTestExecutionListener} must be registered in order * for this annotation to have an effect — for example, via * {@link org.springframework.test.context.TestExecutionListeners @TestExecutionListeners}. * * @author Frank Scheffler + * @author Sam Brannen * @since 5.2 * @see AfterTestClassEvent */ -@Documented @Retention(RUNTIME) @Target({ METHOD, ANNOTATION_TYPE }) +@Documented @EventListener(AfterTestClassEvent.class) public @interface AfterTestClass { + + /** + * Alias for {@link EventListener#condition}. + */ + @AliasFor(annotation = EventListener.class, attribute = "condition") + String value() default ""; + } diff --git a/spring-test/src/main/java/org/springframework/test/context/event/annotation/AfterTestExecution.java b/spring-test/src/main/java/org/springframework/test/context/event/annotation/AfterTestExecution.java index 946a73a811..a7c152eb36 100644 --- a/spring-test/src/main/java/org/springframework/test/context/event/annotation/AfterTestExecution.java +++ b/spring-test/src/main/java/org/springframework/test/context/event/annotation/AfterTestExecution.java @@ -21,6 +21,7 @@ import java.lang.annotation.Retention; import java.lang.annotation.Target; import org.springframework.context.event.EventListener; +import org.springframework.core.annotation.AliasFor; import org.springframework.test.context.event.AfterTestExecutionEvent; import static java.lang.annotation.ElementType.ANNOTATION_TYPE; @@ -41,17 +42,29 @@ import static java.lang.annotation.RetentionPolicy.RUNTIME; * {@link org.springframework.test.context.TestExecutionListener#afterTestExecution} * lifecycle. * + *
Event processing can optionally be made {@linkplain #value conditional} via + * a SpEL expression — for example, + * {@code @AfterTestExecution("event.testContext.testMethod.name matches 'test.*'")}. + * *
The {@code EventPublishingTestExecutionListener} must be registered in order * for this annotation to have an effect — for example, via * {@link org.springframework.test.context.TestExecutionListeners @TestExecutionListeners}. * * @author Frank Scheffler + * @author Sam Brannen * @since 5.2 * @see AfterTestExecutionEvent */ -@Documented @Retention(RUNTIME) @Target({ METHOD, ANNOTATION_TYPE }) +@Documented @EventListener(AfterTestExecutionEvent.class) public @interface AfterTestExecution { + + /** + * Alias for {@link EventListener#condition}. + */ + @AliasFor(annotation = EventListener.class, attribute = "condition") + String value() default ""; + } diff --git a/spring-test/src/main/java/org/springframework/test/context/event/annotation/AfterTestMethod.java b/spring-test/src/main/java/org/springframework/test/context/event/annotation/AfterTestMethod.java index d0da417bb5..a6d8783fec 100644 --- a/spring-test/src/main/java/org/springframework/test/context/event/annotation/AfterTestMethod.java +++ b/spring-test/src/main/java/org/springframework/test/context/event/annotation/AfterTestMethod.java @@ -21,6 +21,7 @@ import java.lang.annotation.Retention; import java.lang.annotation.Target; import org.springframework.context.event.EventListener; +import org.springframework.core.annotation.AliasFor; import org.springframework.test.context.event.AfterTestMethodEvent; import static java.lang.annotation.ElementType.ANNOTATION_TYPE; @@ -41,17 +42,29 @@ import static java.lang.annotation.RetentionPolicy.RUNTIME; * {@link org.springframework.test.context.TestExecutionListener#afterTestMethod} * lifecycle. * + *
Event processing can optionally be made {@linkplain #value conditional} via + * a SpEL expression — for example, + * {@code @AfterTestMethod("event.testContext.testMethod.name matches 'test.*'")}. + * *
The {@code EventPublishingTestExecutionListener} must be registered in order * for this annotation to have an effect — for example, via * {@link org.springframework.test.context.TestExecutionListeners @TestExecutionListeners}. * * @author Frank Scheffler + * @author Sam Brannen * @since 5.2 * @see AfterTestMethodEvent */ -@Documented @Retention(RUNTIME) @Target({ METHOD, ANNOTATION_TYPE }) +@Documented @EventListener(AfterTestMethodEvent.class) public @interface AfterTestMethod { + + /** + * Alias for {@link EventListener#condition}. + */ + @AliasFor(annotation = EventListener.class, attribute = "condition") + String value() default ""; + } diff --git a/spring-test/src/main/java/org/springframework/test/context/event/annotation/BeforeTestClass.java b/spring-test/src/main/java/org/springframework/test/context/event/annotation/BeforeTestClass.java index df58f77291..582e68b23e 100644 --- a/spring-test/src/main/java/org/springframework/test/context/event/annotation/BeforeTestClass.java +++ b/spring-test/src/main/java/org/springframework/test/context/event/annotation/BeforeTestClass.java @@ -21,6 +21,7 @@ import java.lang.annotation.Retention; import java.lang.annotation.Target; import org.springframework.context.event.EventListener; +import org.springframework.core.annotation.AliasFor; import org.springframework.test.context.event.BeforeTestClassEvent; import static java.lang.annotation.ElementType.ANNOTATION_TYPE; @@ -41,17 +42,29 @@ import static java.lang.annotation.RetentionPolicy.RUNTIME; * {@link org.springframework.test.context.TestExecutionListener#beforeTestClass} * lifecycle. * + *
Event processing can optionally be made {@linkplain #value conditional} via + * a SpEL expression — for example, + * {@code @BeforeTestClass("event.testContext.testClass.name matches '.+IntegrationTests'")}. + * *
The {@code EventPublishingTestExecutionListener} must be registered in order * for this annotation to have an effect — for example, via * {@link org.springframework.test.context.TestExecutionListeners @TestExecutionListeners}. * * @author Frank Scheffler + * @author Sam Brannen * @since 5.2 * @see BeforeTestClassEvent */ -@Documented @Retention(RUNTIME) @Target({ METHOD, ANNOTATION_TYPE }) +@Documented @EventListener(BeforeTestClassEvent.class) public @interface BeforeTestClass { + + /** + * Alias for {@link EventListener#condition}. + */ + @AliasFor(annotation = EventListener.class, attribute = "condition") + String value() default ""; + } diff --git a/spring-test/src/main/java/org/springframework/test/context/event/annotation/BeforeTestExecution.java b/spring-test/src/main/java/org/springframework/test/context/event/annotation/BeforeTestExecution.java index e8021737fa..a05ed5eda5 100644 --- a/spring-test/src/main/java/org/springframework/test/context/event/annotation/BeforeTestExecution.java +++ b/spring-test/src/main/java/org/springframework/test/context/event/annotation/BeforeTestExecution.java @@ -21,6 +21,7 @@ import java.lang.annotation.Retention; import java.lang.annotation.Target; import org.springframework.context.event.EventListener; +import org.springframework.core.annotation.AliasFor; import org.springframework.test.context.event.BeforeTestExecutionEvent; import static java.lang.annotation.ElementType.ANNOTATION_TYPE; @@ -41,17 +42,29 @@ import static java.lang.annotation.RetentionPolicy.RUNTIME; * {@link org.springframework.test.context.TestExecutionListener#beforeTestExecution} * lifecycle. * + *
Event processing can optionally be made {@linkplain #value conditional} via + * a SpEL expression — for example, + * {@code @BeforeTestExecution("event.testContext.testMethod.name matches 'test.*'")}. + * *
The {@code EventPublishingTestExecutionListener} must be registered in order * for this annotation to have an effect — for example, via * {@link org.springframework.test.context.TestExecutionListeners @TestExecutionListeners}. * * @author Frank Scheffler + * @author Sam Brannen * @since 5.2 * @see BeforeTestExecutionEvent */ -@Documented @Retention(RUNTIME) @Target({ METHOD, ANNOTATION_TYPE }) +@Documented @EventListener(BeforeTestExecutionEvent.class) public @interface BeforeTestExecution { + + /** + * Alias for {@link EventListener#condition}. + */ + @AliasFor(annotation = EventListener.class, attribute = "condition") + String value() default ""; + } diff --git a/spring-test/src/main/java/org/springframework/test/context/event/annotation/BeforeTestMethod.java b/spring-test/src/main/java/org/springframework/test/context/event/annotation/BeforeTestMethod.java index eef8561286..aaf53c7395 100644 --- a/spring-test/src/main/java/org/springframework/test/context/event/annotation/BeforeTestMethod.java +++ b/spring-test/src/main/java/org/springframework/test/context/event/annotation/BeforeTestMethod.java @@ -21,6 +21,7 @@ import java.lang.annotation.Retention; import java.lang.annotation.Target; import org.springframework.context.event.EventListener; +import org.springframework.core.annotation.AliasFor; import org.springframework.test.context.event.BeforeTestMethodEvent; import static java.lang.annotation.ElementType.ANNOTATION_TYPE; @@ -41,17 +42,29 @@ import static java.lang.annotation.RetentionPolicy.RUNTIME; * {@link org.springframework.test.context.TestExecutionListener#beforeTestMethod} * lifecycle. * + *
Event processing can optionally be made {@linkplain #value conditional} via + * a SpEL expression — for example, + * {@code @BeforeTestMethod("event.testContext.testMethod.name matches 'test.*'")}. + * *
The {@code EventPublishingTestExecutionListener} must be registered in order * for this annotation to have an effect — for example, via * {@link org.springframework.test.context.TestExecutionListeners @TestExecutionListeners}. * * @author Frank Scheffler + * @author Sam Brannen * @since 5.2 * @see BeforeTestMethodEvent */ -@Documented @Retention(RUNTIME) @Target({ METHOD, ANNOTATION_TYPE }) +@Documented @EventListener(BeforeTestMethodEvent.class) public @interface BeforeTestMethod { + + /** + * Alias for {@link EventListener#condition}. + */ + @AliasFor(annotation = EventListener.class, attribute = "condition") + String value() default ""; + } diff --git a/spring-test/src/main/java/org/springframework/test/context/event/annotation/PrepareTestInstance.java b/spring-test/src/main/java/org/springframework/test/context/event/annotation/PrepareTestInstance.java index e45415445e..bbc920115a 100644 --- a/spring-test/src/main/java/org/springframework/test/context/event/annotation/PrepareTestInstance.java +++ b/spring-test/src/main/java/org/springframework/test/context/event/annotation/PrepareTestInstance.java @@ -21,6 +21,7 @@ import java.lang.annotation.Retention; import java.lang.annotation.Target; import org.springframework.context.event.EventListener; +import org.springframework.core.annotation.AliasFor; import org.springframework.test.context.event.PrepareTestInstanceEvent; import static java.lang.annotation.ElementType.ANNOTATION_TYPE; @@ -41,17 +42,29 @@ import static java.lang.annotation.RetentionPolicy.RUNTIME; * {@link org.springframework.test.context.TestExecutionListener#prepareTestInstance} * lifecycle. * + *
Event processing can optionally be made {@linkplain #value conditional} via + * a SpEL expression — for example, + * {@code @PrepareTestInstance("event.testContext.testClass.name matches '.+IntegrationTests'")}. + * *
The {@code EventPublishingTestExecutionListener} must be registered in order * for this annotation to have an effect — for example, via * {@link org.springframework.test.context.TestExecutionListeners @TestExecutionListeners}. * * @author Frank Scheffler + * @author Sam Brannen * @since 5.2 * @see PrepareTestInstanceEvent */ -@Documented @Retention(RUNTIME) @Target({ METHOD, ANNOTATION_TYPE }) +@Documented @EventListener(PrepareTestInstanceEvent.class) public @interface PrepareTestInstance { + + /** + * Alias for {@link EventListener#condition}. + */ + @AliasFor(annotation = EventListener.class, attribute = "condition") + String value() default ""; + } 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 1175eedeaa..59f7830c78 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 @@ -16,6 +16,7 @@ package org.springframework.test.context.event; +import java.lang.annotation.Retention; import java.lang.reflect.Method; import org.junit.Before; @@ -35,8 +36,11 @@ import org.springframework.test.context.event.annotation.BeforeTestClass; import org.springframework.test.context.event.annotation.BeforeTestExecution; import org.springframework.test.context.event.annotation.BeforeTestMethod; import org.springframework.test.context.event.annotation.PrepareTestInstance; +import org.springframework.util.ReflectionUtils; +import static java.lang.annotation.RetentionPolicy.RUNTIME; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; import static org.mockito.Mockito.only; import static org.mockito.Mockito.reset; import static org.mockito.Mockito.verify; @@ -51,11 +55,11 @@ import static org.mockito.Mockito.verify; */ public class EventPublishingTestExecutionListenerIntegrationTests { - private final TestContextManager testContextManager = new TestContextManager(TestCase.class); + private final TestContextManager testContextManager = new TestContextManager(ExampleTestCase.class); private final TestContext testContext = testContextManager.getTestContext(); private final TestExecutionListener listener = testContext.getApplicationContext().getBean(EventCaptureConfiguration.class).listener(); - private final Object testInstance = new TestCase(); - private final Method testMethod = null; + private final Object testInstance = new ExampleTestCase(); + private final Method testMethod = ReflectionUtils.findMethod(ExampleTestCase.class, "test1"); @Before @@ -78,11 +82,18 @@ public class EventPublishingTestExecutionListenerIntegrationTests { } @Test - public void beforeTestMethodAnnotation() throws Exception { + public void beforeTestMethodAnnotationWithMatchingCondition() throws Exception { testContextManager.beforeTestMethod(testInstance, testMethod); verify(listener, only()).beforeTestMethod(testContext); } + @Test + public void beforeTestMethodAnnotationWithFailingCondition() throws Exception { + Method testMethod2 = ReflectionUtils.findMethod(ExampleTestCase.class, "test2"); + testContextManager.beforeTestMethod(testInstance, testMethod2); + verify(listener, never()).beforeTestMethod(testContext); + } + @Test public void beforeTestExecutionAnnotation() throws Exception { testContextManager.beforeTestExecution(testInstance, testMethod); @@ -116,17 +127,17 @@ public class EventPublishingTestExecutionListenerIntegrationTests { return mock(TestExecutionListener.class); } - @BeforeTestClass + @BeforeTestClass("#root.event.source.testClass.name matches '.+TestCase'") public void beforeTestClass(BeforeTestClassEvent e) throws Exception { listener().beforeTestClass(e.getSource()); } - @PrepareTestInstance + @PrepareTestInstance("#a0.testContext.testClass.name matches '.+TestCase'") public void prepareTestInstance(PrepareTestInstanceEvent e) throws Exception { listener().prepareTestInstance(e.getSource()); } - @BeforeTestMethod + @BeforeTestMethod("#p0.testContext.testMethod.isAnnotationPresent(T(org.springframework.test.context.event.EventPublishingTestExecutionListenerIntegrationTests.Traceable))") public void beforeTestMethod(BeforeTestMethodEvent e) throws Exception { listener().beforeTestMethod(e.getSource()); } @@ -141,27 +152,33 @@ public class EventPublishingTestExecutionListenerIntegrationTests { listener().afterTestExecution(e.getSource()); } - @AfterTestMethod + @AfterTestMethod("event.testContext.testMethod.isAnnotationPresent(T(org.springframework.test.context.event.EventPublishingTestExecutionListenerIntegrationTests.Traceable))") public void afterTestMethod(AfterTestMethodEvent e) throws Exception { listener().afterTestMethod(e.getSource()); } - @AfterTestClass - public void afterTestClass(AfterTestClassEvent e) throws Exception { - listener().afterTestClass(e.getSource()); + @AfterTestClass("#afterTestClassEvent.testContext.testClass.name matches '.+TestCase'") + public void afterTestClass(AfterTestClassEvent afterTestClassEvent) throws Exception { + listener().afterTestClass(afterTestClassEvent.getSource()); } } + @Retention(RUNTIME) + @interface Traceable { + } + @ContextConfiguration(classes = EventCaptureConfiguration.class) @TestExecutionListeners(EventPublishingTestExecutionListener.class) - static class TestCase { + static class ExampleTestCase { - /** - * Serves as dummy test method. - */ - @SuppressWarnings("PMD.UncommentedEmptyMethodBody") - public void dummyTestMethod() { + @Traceable + public void test1() { + /* no-op */ + } + + public void test2() { + /* no-op */ } }