summaryrefslogtreecommitdiff
path: root/spring-test/src/main/java/org/springframework/test/context/transaction/TransactionalTestExecutionListener.java
diff options
context:
space:
mode:
Diffstat (limited to 'spring-test/src/main/java/org/springframework/test/context/transaction/TransactionalTestExecutionListener.java')
-rw-r--r--spring-test/src/main/java/org/springframework/test/context/transaction/TransactionalTestExecutionListener.java166
1 files changed, 52 insertions, 114 deletions
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 6ae8d4bd..356eb3d0 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
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 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.
@@ -30,6 +30,8 @@ import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.annotation.BeanFactoryAnnotationUtils;
import org.springframework.core.annotation.AnnotatedElementUtils;
+import org.springframework.core.annotation.AnnotationUtils;
+import org.springframework.test.annotation.Commit;
import org.springframework.test.annotation.Rollback;
import org.springframework.test.context.TestContext;
import org.springframework.test.context.support.AbstractTestExecutionListener;
@@ -43,8 +45,6 @@ import org.springframework.util.Assert;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.StringUtils;
-import static org.springframework.core.annotation.AnnotationUtils.*;
-
/**
* {@code TestExecutionListener} that provides support for executing tests
* within <em>test-managed transactions</em> by honoring Spring's
@@ -83,8 +83,9 @@ import static org.springframework.core.annotation.AnnotationUtils.*;
* <h3>Declarative Rollback and Commit Behavior</h3>
* <p>By default, test transactions will be automatically <em>rolled back</em>
* after completion of the test; however, transactional commit and rollback
- * behavior can be configured declaratively via the {@link Rollback @Rollback}
- * annotation at the class level and at the method level.
+ * behavior can be configured declaratively via the {@link Commit @Commit}
+ * and {@link Rollback @Rollback} annotations at the class level and at the
+ * method level.
*
* <h3>Programmatic Transaction Management</h3>
* <p>As of Spring Framework 4.1, it is possible to interact with test-managed
@@ -96,9 +97,10 @@ import static org.springframework.core.annotation.AnnotationUtils.*;
* <p>When executing transactional tests, it is sometimes useful to be able to
* execute certain <em>set up</em> or <em>tear down</em> 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.
*
* <h3>Configuring a Transaction Manager</h3>
* <p>{@code TransactionalTestExecutionListener} expects a
@@ -133,7 +135,8 @@ public class TransactionalTestExecutionListener extends AbstractTestExecutionLis
@SuppressWarnings("deprecation")
private static final TransactionConfigurationAttributes defaultTxConfigAttributes = new TransactionConfigurationAttributes();
- protected final TransactionAttributeSource attributeSource = new AnnotationTransactionAttributeSource();
+ // Do not require @Transactional test methods to be public.
+ protected final TransactionAttributeSource attributeSource = new AnnotationTransactionAttributeSource(false);
@SuppressWarnings("deprecation")
private TransactionConfigurationAttributes configurationAttributes;
@@ -177,8 +180,8 @@ public class TransactionalTestExecutionListener extends AbstractTestExecutionLis
transactionAttribute);
if (logger.isDebugEnabled()) {
- logger.debug("Explicit transaction definition [" + transactionAttribute + "] found for test context "
- + testContext);
+ logger.debug("Explicit transaction definition [" + transactionAttribute + "] found for test context " +
+ testContext);
}
if (transactionAttribute.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NOT_SUPPORTED) {
@@ -189,8 +192,8 @@ public class TransactionalTestExecutionListener extends AbstractTestExecutionLis
if (tm == null) {
throw new IllegalStateException(String.format(
- "Failed to retrieve PlatformTransactionManager for @Transactional test for test context %s.",
- testContext));
+ "Failed to retrieve PlatformTransactionManager for @Transactional test for test context %s.",
+ testContext));
}
}
@@ -246,12 +249,15 @@ public class TransactionalTestExecutionListener extends AbstractTestExecutionLis
if (logger.isDebugEnabled()) {
logger.debug("Executing @BeforeTransaction method [" + method + "] for test context " + testContext);
}
+ ReflectionUtils.makeAccessible(method);
method.invoke(testContext.getTestInstance());
}
}
catch (InvocationTargetException ex) {
- logger.error("Exception encountered while executing @BeforeTransaction methods for test context "
- + testContext + ".", ex.getTargetException());
+ if (logger.isErrorEnabled()) {
+ logger.error("Exception encountered while executing @BeforeTransaction methods for test context " +
+ testContext + ".", ex.getTargetException());
+ }
ReflectionUtils.rethrowException(ex.getTargetException());
}
}
@@ -273,6 +279,7 @@ public class TransactionalTestExecutionListener extends AbstractTestExecutionLis
if (logger.isDebugEnabled()) {
logger.debug("Executing @AfterTransaction method [" + method + "] for test context " + testContext);
}
+ ReflectionUtils.makeAccessible(method);
method.invoke(testContext.getTestInstance());
}
catch (InvocationTargetException ex) {
@@ -280,15 +287,15 @@ public class TransactionalTestExecutionListener extends AbstractTestExecutionLis
if (afterTransactionException == null) {
afterTransactionException = targetException;
}
- logger.error("Exception encountered while executing @AfterTransaction method [" + method
- + "] for test context " + testContext, targetException);
+ logger.error("Exception encountered while executing @AfterTransaction method [" + method +
+ "] for test context " + testContext, targetException);
}
catch (Exception ex) {
if (afterTransactionException == null) {
afterTransactionException = ex;
}
- logger.error("Exception encountered while executing @AfterTransaction method [" + method
- + "] for test context " + testContext, ex);
+ logger.error("Exception encountered while executing @AfterTransaction method [" + method +
+ "] for test context " + testContext, ex);
}
}
@@ -311,20 +318,18 @@ public class TransactionalTestExecutionListener extends AbstractTestExecutionLis
* @see #getTransactionManager(TestContext)
*/
protected PlatformTransactionManager getTransactionManager(TestContext testContext, String qualifier) {
- // look up by type and qualifier from @Transactional
+ // Look up by type and qualifier from @Transactional
if (StringUtils.hasText(qualifier)) {
try {
- // Use autowire-capable factory in order to support extended qualifier
- // matching (only exposed on the internal BeanFactory, not on the
- // ApplicationContext).
+ // Use autowire-capable factory in order to support extended qualifier matching
+ // (only exposed on the internal BeanFactory, not on the ApplicationContext).
BeanFactory bf = testContext.getApplicationContext().getAutowireCapableBeanFactory();
return BeanFactoryAnnotationUtils.qualifiedBeanOfType(bf, PlatformTransactionManager.class, qualifier);
}
catch (RuntimeException ex) {
if (logger.isWarnEnabled()) {
- logger.warn(
- String.format(
+ logger.warn(String.format(
"Caught exception while retrieving transaction manager with qualifier '%s' for test context %s",
qualifier, testContext), ex);
}
@@ -359,7 +364,7 @@ public class TransactionalTestExecutionListener extends AbstractTestExecutionLis
/**
* Determine whether or not to rollback transactions by default for the
* supplied {@linkplain TestContext test context}.
- * <p>Supports {@link Rollback @Rollback} or
+ * <p>Supports {@link Rollback @Rollback}, {@link Commit @Commit}, or
* {@link TransactionConfiguration @TransactionConfiguration} at the
* class-level.
* @param testContext the test context for which the default rollback flag
@@ -370,7 +375,7 @@ public class TransactionalTestExecutionListener extends AbstractTestExecutionLis
@SuppressWarnings("deprecation")
protected final boolean isDefaultRollback(TestContext testContext) throws Exception {
Class<?> testClass = testContext.getTestClass();
- Rollback rollback = findAnnotation(testClass, Rollback.class);
+ Rollback rollback = AnnotatedElementUtils.findMergedAnnotation(testClass, Rollback.class);
boolean rollbackPresent = (rollback != null);
TransactionConfigurationAttributes txConfigAttributes = retrieveConfigurationAttributes(testContext);
@@ -405,111 +410,45 @@ public class TransactionalTestExecutionListener extends AbstractTestExecutionLis
*/
protected final boolean isRollback(TestContext testContext) throws Exception {
boolean rollback = isDefaultRollback(testContext);
- Rollback rollbackAnnotation = findAnnotation(testContext.getTestMethod(), Rollback.class);
+ Rollback rollbackAnnotation =
+ AnnotatedElementUtils.findMergedAnnotation(testContext.getTestMethod(), Rollback.class);
if (rollbackAnnotation != null) {
boolean rollbackOverride = rollbackAnnotation.value();
if (logger.isDebugEnabled()) {
logger.debug(String.format(
- "Method-level @Rollback(%s) overrides default rollback [%s] for test context %s.",
- rollbackOverride, rollback, testContext));
+ "Method-level @Rollback(%s) overrides default rollback [%s] for test context %s.",
+ rollbackOverride, rollback, testContext));
}
rollback = rollbackOverride;
}
else {
if (logger.isDebugEnabled()) {
logger.debug(String.format(
- "No method-level @Rollback override: using default rollback [%s] for test context %s.", rollback,
- testContext));
+ "No method-level @Rollback override: using default rollback [%s] for test context %s.",
+ rollback, testContext));
}
}
return rollback;
}
/**
- * 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}.
- * <p>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<Class<?>> getSuperClasses(Class<?> clazz) {
- List<Class<?>> results = new ArrayList<Class<?>>();
- 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 <em>shadowed</em> by methods overridden in subclasses.
- * <p>Note: This code has been borrowed from
- * {@link org.junit.internal.runners.TestClass#getAnnotatedMethods(Class)}
- * and adapted.
+ * <p>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<Method> getAnnotatedMethods(Class<?> clazz, Class<? extends Annotation> annotationType) {
- List<Method> results = new ArrayList<Method>();
- for (Class<?> current : getSuperClasses(clazz)) {
- for (Method method : current.getDeclaredMethods()) {
- Annotation annotation = getAnnotation(method, annotationType);
- if (annotation != null && !isShadowed(method, results)) {
- results.add(method);
- }
+ List<Method> methods = new ArrayList<Method>(4);
+ for (Method method : ReflectionUtils.getUniqueDeclaredMethods(clazz)) {
+ if (AnnotationUtils.getAnnotation(method, annotationType) != null) {
+ methods.add(method);
}
}
- return results;
- }
-
- /**
- * Determine if the supplied {@link Method method} is <em>shadowed</em> by
- * a method in the supplied {@link List list} of previous methods.
- * <p>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<Method> previousMethods) {
- for (Method each : previousMethods) {
- if (isShadowed(method, each)) {
- return true;
- }
- }
- return false;
- }
-
- /**
- * Determine if the supplied {@linkplain Method current method} is
- * <em>shadowed</em> by a {@linkplain Method previous method}.
- * <p>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;
- }
- }
- return true;
+ return methods;
}
/**
@@ -531,19 +470,18 @@ public class TransactionalTestExecutionListener extends AbstractTestExecutionLis
if (this.configurationAttributes == null) {
Class<?> clazz = testContext.getTestClass();
- TransactionConfiguration txConfig = AnnotatedElementUtils.findMergedAnnotation(clazz,
- TransactionConfiguration.class);
+ TransactionConfiguration txConfig =
+ AnnotatedElementUtils.findMergedAnnotation(clazz, TransactionConfiguration.class);
if (logger.isDebugEnabled()) {
logger.debug(String.format("Retrieved @TransactionConfiguration [%s] for test class [%s].",
- txConfig, clazz.getName()));
+ txConfig, clazz.getName()));
}
- TransactionConfigurationAttributes configAttributes = (txConfig == null ? defaultTxConfigAttributes
- : new TransactionConfigurationAttributes(txConfig.transactionManager(), txConfig.defaultRollback()));
-
+ TransactionConfigurationAttributes configAttributes = (txConfig == null ? defaultTxConfigAttributes :
+ new TransactionConfigurationAttributes(txConfig.transactionManager(), txConfig.defaultRollback()));
if (logger.isDebugEnabled()) {
logger.debug(String.format("Using TransactionConfigurationAttributes %s for test class [%s].",
- configAttributes, clazz.getName()));
+ configAttributes, clazz.getName()));
}
this.configurationAttributes = configAttributes;
}