From 0f6711fe3b9945304a6fcb1a4d0bde28de49dbd2 Mon Sep 17 00:00:00 2001 From: Sam Brannen Date: Tue, 3 May 2016 18:53:17 +0200 Subject: [PATCH] Support @[Before|After]Transaction on default methods Prior to this commit, @BeforeTransaction and @AfterTransaction could only be declared on methods within test classes. However, JUnit 5 as well as some existing third-party Runner implementations for JUnit 4 already support Java 8 based interface default methods in various scenarios -- for example, @Test, @BeforeEach, etc. This commit brings the Spring TestContext Framework up to date by supporting the declaration of @BeforeTransaction and @AfterTransaction on interface default methods. Issue: SPR-14183 --- .../context/transaction/AfterTransaction.java | 12 ++- .../transaction/BeforeTransaction.java | 12 ++- .../TransactionalTestExecutionListener.java | 90 +++---------------- ...ansactionalTestExecutionListenerTests.java | 3 - src/asciidoc/testing.adoc | 25 +++--- src/asciidoc/whats-new.adoc | 5 +- 6 files changed, 46 insertions(+), 101 deletions(-) diff --git a/spring-test/src/main/java/org/springframework/test/context/transaction/AfterTransaction.java b/spring-test/src/main/java/org/springframework/test/context/transaction/AfterTransaction.java index aeaac1355bf..a7f6653e886 100644 --- a/spring-test/src/main/java/org/springframework/test/context/transaction/AfterTransaction.java +++ b/spring-test/src/main/java/org/springframework/test/context/transaction/AfterTransaction.java @@ -23,12 +23,16 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** - *

Test annotation to indicate that the annotated {@code void} method + *

Test annotation which indicates that the annotated {@code void} method * should be executed after a transaction is ended for a test method - * configured to run within a transaction via the {@code @Transactional} annotation. + * configured to run within a transaction via Spring's {@code @Transactional} + * annotation. * - *

The {@code @AfterTransaction} methods of superclasses will be executed - * after those of the current class. + *

As of Spring Framework 4.3, {@code @AfterTransaction} may be declared on + * Java 8 based interface default methods. + * + *

{@code @AfterTransaction} methods declared in superclasses or as interface + * default methods will be executed after those of the current test class. * *

As of Spring Framework 4.0, this annotation may be used as a * meta-annotation to create custom composed annotations. diff --git a/spring-test/src/main/java/org/springframework/test/context/transaction/BeforeTransaction.java b/spring-test/src/main/java/org/springframework/test/context/transaction/BeforeTransaction.java index 418a077bf4f..217c7d1768b 100644 --- a/spring-test/src/main/java/org/springframework/test/context/transaction/BeforeTransaction.java +++ b/spring-test/src/main/java/org/springframework/test/context/transaction/BeforeTransaction.java @@ -23,12 +23,16 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** - *

Test annotation to indicate that the annotated {@code void} method + *

Test annotation which indicates that the annotated {@code void} method * should be executed before a transaction is started for a test method - * configured to run within a transaction via the {@code @Transactional} annotation. + * configured to run within a transaction via Spring's {@code @Transactional} + * annotation. * - *

The {@code @BeforeTransaction} methods of superclasses will be executed - * before those of the current class. + *

As of Spring Framework 4.3, {@code @BeforeTransaction} may be declared on + * Java 8 based interface default methods. + * + *

{@code @BeforeTransaction} methods declared in superclasses or as interface + * default methods will be executed before those of the current test class. * *

As of Spring Framework 4.0, this annotation may be used as a * meta-annotation to create custom composed annotations. diff --git a/spring-test/src/main/java/org/springframework/test/context/transaction/TransactionalTestExecutionListener.java b/spring-test/src/main/java/org/springframework/test/context/transaction/TransactionalTestExecutionListener.java index d81f1ddaa49..adb3cfa144e 100644 --- a/spring-test/src/main/java/org/springframework/test/context/transaction/TransactionalTestExecutionListener.java +++ b/spring-test/src/main/java/org/springframework/test/context/transaction/TransactionalTestExecutionListener.java @@ -97,9 +97,10 @@ import org.springframework.util.StringUtils; *

When executing transactional tests, it is sometimes useful to be able to * execute certain set up or tear down code outside of a * transaction. {@code TransactionalTestExecutionListener} provides such - * support for methods annotated with - * {@link BeforeTransaction @BeforeTransaction} or - * {@link AfterTransaction @AfterTransaction}. + * support for methods annotated with {@link BeforeTransaction @BeforeTransaction} + * or {@link AfterTransaction @AfterTransaction}. As of Spring Framework 4.3, + * {@code @BeforeTransaction} and {@code @AfterTransaction} may also be declared + * on Java 8 based interface default methods. * *

Configuring a Transaction Manager

*

{@code TransactionalTestExecutionListener} expects a @@ -431,90 +432,23 @@ public class TransactionalTestExecutionListener extends AbstractTestExecutionLis } /** - * Gets all superclasses of the supplied {@link Class class}, including the - * class itself. The ordering of the returned list will begin with the - * supplied class and continue up the class hierarchy, excluding {@link Object}. - *

Note: This code has been borrowed from - * {@link org.junit.internal.runners.TestClass#getSuperClasses(Class)} and - * adapted. - * @param clazz the class for which to retrieve the superclasses - * @return all superclasses of the supplied class, excluding {@code Object} - */ - private List> getSuperClasses(Class clazz) { - List> results = new ArrayList>(); - Class current = clazz; - while (current != null && Object.class != current) { - results.add(current); - current = current.getSuperclass(); - } - return results; - } - - /** - * Gets all methods in the supplied {@link Class class} and its superclasses + * Get all methods in the supplied {@link Class class} and its superclasses * which are annotated with the supplied {@code annotationType} but * which are not shadowed by methods overridden in subclasses. - *

Note: This code has been borrowed from - * {@link org.junit.internal.runners.TestClass#getAnnotatedMethods(Class)} - * and adapted. + *

Default methods on interfaces are also detected. * @param clazz the class for which to retrieve the annotated methods * @param annotationType the annotation type for which to search * @return all annotated methods in the supplied class and its superclasses + * as well as annotated interface default methods */ private List getAnnotatedMethods(Class clazz, Class annotationType) { - List results = new ArrayList(); - for (Class current : getSuperClasses(clazz)) { - for (Method method : current.getDeclaredMethods()) { - Annotation annotation = AnnotationUtils.getAnnotation(method, annotationType); - if (annotation != null && !isShadowed(method, results)) { - results.add(method); - } - } - } - return results; - } - - /** - * Determine if the supplied {@link Method method} is shadowed by - * a method in the supplied {@link List list} of previous methods. - *

Note: This code has been borrowed from - * {@link org.junit.internal.runners.TestClass#isShadowed(Method, List)}. - * @param method the method to check for shadowing - * @param previousMethods the list of methods which have previously been processed - * @return {@code true} if the supplied method is shadowed by a - * method in the {@code previousMethods} list - */ - private boolean isShadowed(Method method, List previousMethods) { - for (Method each : previousMethods) { - if (isShadowed(method, each)) { - return true; - } - } - return false; - } - - /** - * Determine if the supplied {@linkplain Method current method} is - * shadowed by a {@linkplain Method previous method}. - *

Note: This code has been borrowed from - * {@link org.junit.internal.runners.TestClass#isShadowed(Method, Method)}. - * @param current the current method - * @param previous the previous method - * @return {@code true} if the previous method shadows the current one - */ - private boolean isShadowed(Method current, Method previous) { - if (!previous.getName().equals(current.getName())) { - return false; - } - if (previous.getParameterTypes().length != current.getParameterTypes().length) { - return false; - } - for (int i = 0; i < previous.getParameterTypes().length; i++) { - if (!previous.getParameterTypes()[i].equals(current.getParameterTypes()[i])) { - return false; + List methods = new ArrayList(4); + for (Method method : ReflectionUtils.getUniqueDeclaredMethods(clazz)) { + if (AnnotationUtils.getAnnotation(method, annotationType) != null) { + methods.add(method); } } - return true; + return methods; } /** diff --git a/spring-test/src/test/java/org/springframework/test/context/transaction/TransactionalTestExecutionListenerTests.java b/spring-test/src/test/java/org/springframework/test/context/transaction/TransactionalTestExecutionListenerTests.java index 31975c73fdb..0e4f7276f8a 100644 --- a/spring-test/src/test/java/org/springframework/test/context/transaction/TransactionalTestExecutionListenerTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/transaction/TransactionalTestExecutionListenerTests.java @@ -20,7 +20,6 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import org.junit.After; -import org.junit.Ignore; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; @@ -245,13 +244,11 @@ public class TransactionalTestExecutionListenerTests { assertAfterTestMethod(AfterTransactionDeclaredViaMetaAnnotationTestCase.class); } - @Ignore("Disabled until @BeforeTransaction is supported on interface default methods") @Test public void beforeTestMethodWithBeforeTransactionDeclaredAsInterfaceDefaultMethod() throws Exception { assertBeforeTestMethod(BeforeTransactionDeclaredAsInterfaceDefaultMethodTestCase.class); } - @Ignore("Disabled until @AfterTransaction is supported on interface default methods") @Test public void afterTestMethodWithAfterTransactionDeclaredAsInterfaceDefaultMethod() throws Exception { assertAfterTestMethod(AfterTransactionDeclaredAsInterfaceDefaultMethodTestCase.class); diff --git a/src/asciidoc/testing.adoc b/src/asciidoc/testing.adoc index baa0c8a4ed7..1156eb715d4 100644 --- a/src/asciidoc/testing.adoc +++ b/src/asciidoc/testing.adoc @@ -854,9 +854,12 @@ method, potentially overriding class-level `@Rollback` or `@Commit` semantics. + -Indicates that the annotated `void` method should be executed __before__ a -transaction is started for test methods configured to run within a transaction via the -`@Transactional` annotation. +Indicates that the annotated `void` method should be executed __before__ a transaction +is started for test methods configured to run within a transaction via Spring's +`@Transactional` annotation. As of Spring Framework 4.3, `@BeforeTransaction` methods +are not required to be `public` and may be declared on Java 8 based interface default +methods. + + @@ -873,9 +876,11 @@ transaction is started for test methods configured to run within a transaction v + -Indicates that the annotated `void` method should be executed __after__ a -transaction has ended for test methods configured to run within a transaction via the -`@Transactional` annotation. +Indicates that the annotated `void` method should be executed __after__ a transaction +is ended for test methods configured to run within a transaction via Spring's +`@Transactional` annotation. As of Spring Framework 4.3, `@AfterTransaction` methods +are not required to be `public` and may be declared on Java 8 based interface default +methods. + @@ -3210,12 +3215,12 @@ javadocs for `TestTransaction` for further details. Occasionally you need to execute certain code before or after a transactional test method but outside the transactional context -- for example, to verify the initial database state prior to execution of your test or to verify expected transactional commit behavior after -test execution (if the test was configured not to roll back the transaction). +test execution (if the test was configured to commit the transaction). `TransactionalTestExecutionListener` supports the `@BeforeTransaction` and `@AfterTransaction` annotations exactly for such scenarios. Simply annotate any `void` -method in your test class with one of these annotations, and the -`TransactionalTestExecutionListener` ensures that your __before transaction method__ or -__after transaction method__ is executed at the appropriate time. +method in a test class or any `void` default method in a test interface with one of these +annotations, and the `TransactionalTestExecutionListener` ensures that your __before +transaction method__ or __after transaction method__ is executed at the appropriate time. [TIP] ==== diff --git a/src/asciidoc/whats-new.adoc b/src/asciidoc/whats-new.adoc index 03fa76f07dd..17b4d8f4f0e 100644 --- a/src/asciidoc/whats-new.adoc +++ b/src/asciidoc/whats-new.adoc @@ -694,8 +694,9 @@ Spring 4.3 also improves the caching abstraction as follows: * New `SpringRunner` _alias_ for the `SpringJUnit4ClassRunner`. * An empty declaration of `@ContextConfiguration` can now be completely omitted if default XML files, Groovy scripts, or `@Configuration` classes are detected. -* `@Transactional` test methods are no longer required to be `public` (in TestNG and JUnit 5). -* `@BeforeTransaction` and `@AfterTransaction` methods are no longer required to be `public`. +* `@Transactional` test methods are no longer required to be `public` (e.g., in TestNG and JUnit 5). +* `@BeforeTransaction` and `@AfterTransaction` methods are no longer required to be `public` + and may now be declared on Java 8 based interface default methods. * The `ApplicationContext` cache in the _Spring TestContext Framework_ is now bounded with a default maximum size of 32 and a _least recently used_ eviction policy. The maximum size can be configured by setting a JVM system property or Spring property called