From a0cc80063dfa15cd7898855e5c98c416aca5b258 Mon Sep 17 00:00:00 2001 From: Juergen Hoeller Date: Mon, 5 Mar 2018 13:00:35 +0100 Subject: [PATCH] Concurrency and exception message refinements for test transactions --- .../context/transaction/AfterTransaction.java | 8 +- .../transaction/BeforeTransaction.java | 8 +- .../TestContextTransactionUtils.java | 61 +++--- .../context/transaction/TestTransaction.java | 21 +- .../transaction/TransactionContext.java | 24 ++- .../transaction/TransactionContextHolder.java | 12 +- .../TransactionalTestExecutionListener.java | 20 +- .../PrimaryTransactionManagerTests.java | 76 ++++--- ...ansactionalTestExecutionListenerTests.java | 194 ++++++++---------- 9 files changed, 194 insertions(+), 230 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 a7f6653e88..b740509c56 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 @@ -1,5 +1,5 @@ /* - * Copyright 2002-2016 the original author or authors. + * Copyright 2002-2018 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. @@ -28,15 +28,15 @@ import java.lang.annotation.Target; * configured to run within a transaction via Spring's {@code @Transactional} * annotation. * - *

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. * + *

As of Spring Framework 4.3, {@code @AfterTransaction} may also be + * declared on Java 8 based interface default methods. + * * @author Sam Brannen * @since 2.5 * @see org.springframework.transaction.annotation.Transactional 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 217c7d1768..c13fa66c8a 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 @@ -1,5 +1,5 @@ /* - * Copyright 2002-2016 the original author or authors. + * Copyright 2002-2018 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. @@ -28,15 +28,15 @@ import java.lang.annotation.Target; * configured to run within a transaction via Spring's {@code @Transactional} * annotation. * - *

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. * + *

As of Spring Framework 4.3, {@code @BeforeTransaction} may also be + * declared on Java 8 based interface default methods. + * * @author Sam Brannen * @since 2.5 * @see org.springframework.transaction.annotation.Transactional diff --git a/spring-test/src/main/java/org/springframework/test/context/transaction/TestContextTransactionUtils.java b/spring-test/src/main/java/org/springframework/test/context/transaction/TestContextTransactionUtils.java index 8847c7beb8..8bf0a465c3 100644 --- a/spring-test/src/main/java/org/springframework/test/context/transaction/TestContextTransactionUtils.java +++ b/spring-test/src/main/java/org/springframework/test/context/transaction/TestContextTransactionUtils.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2016 the original author or authors. + * Copyright 2002-2018 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. @@ -17,7 +17,6 @@ package org.springframework.test.context.transaction; import java.util.Map; - import javax.sql.DataSource; import org.apache.commons.logging.Log; @@ -40,6 +39,7 @@ import org.springframework.util.StringUtils; /** * Utility methods for working with transactions and data access related beans * within the Spring TestContext Framework. + * *

Mainly for internal use within the framework. * * @author Sam Brannen @@ -48,8 +48,6 @@ import org.springframework.util.StringUtils; */ public abstract class TestContextTransactionUtils { - private static final Log logger = LogFactory.getLog(TestContextTransactionUtils.class); - /** * Default bean name for a {@link DataSource}: {@code "dataSource"}. */ @@ -62,9 +60,8 @@ public abstract class TestContextTransactionUtils { public static final String DEFAULT_TRANSACTION_MANAGER_NAME = "transactionManager"; - private TestContextTransactionUtils() { - /* prevent instantiation */ - } + private static final Log logger = LogFactory.getLog(TestContextTransactionUtils.class); + /** * Retrieve the {@link DataSource} to use for the supplied {@linkplain TestContext @@ -82,8 +79,8 @@ public abstract class TestContextTransactionUtils { * {@linkplain #DEFAULT_DATA_SOURCE_NAME default data source name}. * @param testContext the test context for which the {@code DataSource} * should be retrieved; never {@code null} - * @param name the name of the {@code DataSource} to retrieve; may be {@code null} - * or empty + * @param name the name of the {@code DataSource} to retrieve + * (may be {@code null} or empty) * @return the {@code DataSource} to use, or {@code null} if not found * @throws BeansException if an error occurs while retrieving an explicitly * named {@code DataSource} @@ -94,14 +91,14 @@ public abstract class TestContextTransactionUtils { BeanFactory bf = testContext.getApplicationContext().getAutowireCapableBeanFactory(); try { - // look up by type and explicit name + // Look up by type and explicit name if (StringUtils.hasText(name)) { return bf.getBean(name, DataSource.class); } } catch (BeansException ex) { - logger.error( - String.format("Failed to retrieve DataSource named '%s' for test context %s", name, testContext), ex); + logger.error(String.format("Failed to retrieve DataSource named '%s' for test context %s", + name, testContext), ex); throw ex; } @@ -109,9 +106,9 @@ public abstract class TestContextTransactionUtils { if (bf instanceof ListableBeanFactory) { ListableBeanFactory lbf = (ListableBeanFactory) bf; - // look up single bean by type - Map dataSources = BeanFactoryUtils.beansOfTypeIncludingAncestors(lbf, - DataSource.class); + // Look up single bean by type + Map dataSources = + BeanFactoryUtils.beansOfTypeIncludingAncestors(lbf, DataSource.class); if (dataSources.size() == 1) { return dataSources.values().iterator().next(); } @@ -153,8 +150,8 @@ public abstract class TestContextTransactionUtils { * name}. * @param testContext the test context for which the transaction manager * should be retrieved; never {@code null} - * @param name the name of the transaction manager to retrieve; may be - * {@code null} or empty + * @param name the name of the transaction manager to retrieve + * (may be {@code null} or empty) * @return the transaction manager to use, or {@code null} if not found * @throws BeansException if an error occurs while retrieving an explicitly * named transaction manager @@ -167,14 +164,14 @@ public abstract class TestContextTransactionUtils { BeanFactory bf = testContext.getApplicationContext().getAutowireCapableBeanFactory(); try { - // look up by type and explicit name + // Look up by type and explicit name if (StringUtils.hasText(name)) { return bf.getBean(name, PlatformTransactionManager.class); } } catch (BeansException ex) { - logger.error(String.format("Failed to retrieve transaction manager named '%s' for test context %s", name, - testContext), ex); + logger.error(String.format("Failed to retrieve transaction manager named '%s' for test context %s", + name, testContext), ex); throw ex; } @@ -182,24 +179,24 @@ public abstract class TestContextTransactionUtils { if (bf instanceof ListableBeanFactory) { ListableBeanFactory lbf = (ListableBeanFactory) bf; - // look up single bean by type - Map txMgrs = BeanFactoryUtils.beansOfTypeIncludingAncestors(lbf, - PlatformTransactionManager.class); + // Look up single bean by type + Map txMgrs = + BeanFactoryUtils.beansOfTypeIncludingAncestors(lbf, PlatformTransactionManager.class); if (txMgrs.size() == 1) { return txMgrs.values().iterator().next(); } try { - // look up single bean by type, with support for 'primary' beans + // Look up single bean by type, with support for 'primary' beans return bf.getBean(PlatformTransactionManager.class); } catch (BeansException ex) { logBeansException(testContext, ex, PlatformTransactionManager.class); } - // look up single TransactionManagementConfigurer - Map configurers = BeanFactoryUtils.beansOfTypeIncludingAncestors( - lbf, TransactionManagementConfigurer.class); + // Look up single TransactionManagementConfigurer + Map configurers = + BeanFactoryUtils.beansOfTypeIncludingAncestors(lbf, TransactionManagementConfigurer.class); Assert.state(configurers.size() <= 1, "Only one TransactionManagementConfigurer may exist in the ApplicationContext"); if (configurers.size() == 1) { @@ -227,13 +224,13 @@ public abstract class TestContextTransactionUtils { * Create a delegating {@link TransactionAttribute} for the supplied target * {@link TransactionAttribute} and {@link TestContext}, using the names of * the test class and test method to build the name of the transaction. - * - * @param testContext the {@code TestContext} upon which to base the name; never {@code null} - * @param targetAttribute the {@code TransactionAttribute} to delegate to; never {@code null} + * @param testContext the {@code TestContext} upon which to base the name + * @param targetAttribute the {@code TransactionAttribute} to delegate to * @return the delegating {@code TransactionAttribute} */ - public static TransactionAttribute createDelegatingTransactionAttribute(TestContext testContext, - TransactionAttribute targetAttribute) { + public static TransactionAttribute createDelegatingTransactionAttribute( + TestContext testContext, TransactionAttribute targetAttribute) { + Assert.notNull(testContext, "TestContext must not be null"); Assert.notNull(targetAttribute, "Target TransactionAttribute must not be null"); return new TestContextTransactionAttribute(targetAttribute, testContext); diff --git a/spring-test/src/main/java/org/springframework/test/context/transaction/TestTransaction.java b/spring-test/src/main/java/org/springframework/test/context/transaction/TestTransaction.java index c0a737a5d7..d238ed800f 100644 --- a/spring-test/src/main/java/org/springframework/test/context/transaction/TestTransaction.java +++ b/spring-test/src/main/java/org/springframework/test/context/transaction/TestTransaction.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2016 the original author or authors. + * Copyright 2002-2018 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. @@ -50,10 +50,8 @@ public class TestTransaction { TransactionContext transactionContext = TransactionContextHolder.getCurrentTransactionContext(); if (transactionContext != null) { TransactionStatus transactionStatus = transactionContext.getTransactionStatus(); - return (transactionStatus != null) && (!transactionStatus.isCompleted()); + return (transactionStatus != null && !transactionStatus.isCompleted()); } - - // else return false; } @@ -80,8 +78,7 @@ public class TestTransaction { * Rather, the value of this flag will be used to determine whether or not * the current test-managed transaction should be rolled back or committed * once it is {@linkplain #end ended}. - * @throws IllegalStateException if a transaction is not active for the - * current test + * @throws IllegalStateException if no transaction is active for the current test * @see #isActive() * @see #isFlaggedForRollback() * @see #start() @@ -97,8 +94,7 @@ public class TestTransaction { * Rather, the value of this flag will be used to determine whether or not * the current test-managed transaction should be rolled back or committed * once it is {@linkplain #end ended}. - * @throws IllegalStateException if a transaction is not active for the - * current test + * @throws IllegalStateException if no transaction is active for the current test * @see #isActive() * @see #isFlaggedForRollback() * @see #start() @@ -122,9 +118,9 @@ public class TestTransaction { } /** - * Immediately force a commit or rollback of the current - * test-managed transaction, according to the {@linkplain #isFlaggedForRollback - * rollback flag}. + * Immediately force a commit or rollback of the + * current test-managed transaction, according to the + * {@linkplain #isFlaggedForRollback rollback flag}. * @throws IllegalStateException if the transaction context could not be * retrieved or if a transaction is not active for the current test * @see #isActive() @@ -134,6 +130,7 @@ public class TestTransaction { requireCurrentTransactionContext().endTransaction(); } + private static TransactionContext requireCurrentTransactionContext() { TransactionContext txContext = TransactionContextHolder.getCurrentTransactionContext(); Assert.state(txContext != null, "TransactionContext is not active"); @@ -144,4 +141,4 @@ public class TestTransaction { requireCurrentTransactionContext().setFlaggedForRollback(flag); } -} \ No newline at end of file +} diff --git a/spring-test/src/main/java/org/springframework/test/context/transaction/TransactionContext.java b/spring-test/src/main/java/org/springframework/test/context/transaction/TransactionContext.java index 5b41da14fb..1692ab36e5 100644 --- a/spring-test/src/main/java/org/springframework/test/context/transaction/TransactionContext.java +++ b/spring-test/src/main/java/org/springframework/test/context/transaction/TransactionContext.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2017 the original author or authors. + * Copyright 2002-2018 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. @@ -16,6 +16,8 @@ package org.springframework.test.context.transaction; +import java.util.concurrent.atomic.AtomicInteger; + import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -53,7 +55,7 @@ class TransactionContext { @Nullable private TransactionStatus transactionStatus; - private volatile int transactionsStarted = 0; + private final AtomicInteger transactionsStarted = new AtomicInteger(0); TransactionContext(TestContext testContext, PlatformTransactionManager transactionManager, @@ -82,8 +84,8 @@ class TransactionContext { } void setFlaggedForRollback(boolean flaggedForRollback) { - Assert.state(this.transactionStatus != null, () -> String.format( - "Failed to set rollback flag for test context %s: transaction does not exist.", this.testContext)); + Assert.state(this.transactionStatus != null, () -> + "Failed to set rollback flag - transaction does not exist: " + this.testContext); this.flaggedForRollback = flaggedForRollback; } @@ -95,16 +97,16 @@ class TransactionContext { */ void startTransaction() { Assert.state(this.transactionStatus == null, - "Cannot start a new transaction without ending the existing transaction first."); + "Cannot start a new transaction without ending the existing transaction first"); this.flaggedForRollback = this.defaultRollback; this.transactionStatus = this.transactionManager.getTransaction(this.transactionDefinition); - ++this.transactionsStarted; + int transactionsStarted = this.transactionsStarted.incrementAndGet(); if (logger.isInfoEnabled()) { logger.info(String.format( "Began transaction (%s) for test context %s; transaction manager [%s]; rollback [%s]", - this.transactionsStarted, this.testContext, this.transactionManager, flaggedForRollback)); + transactionsStarted, this.testContext, this.transactionManager, flaggedForRollback)); } } @@ -118,8 +120,8 @@ class TransactionContext { "Ending transaction for test context %s; transaction status [%s]; rollback [%s]", this.testContext, this.transactionStatus, this.flaggedForRollback)); } - Assert.state(this.transactionStatus != null, () -> String.format( - "Failed to end transaction for test context %s: transaction does not exist.", this.testContext)); + Assert.state(this.transactionStatus != null, + () -> "Failed to end transaction - transaction does not exist: " + this.testContext); try { if (this.flaggedForRollback) { @@ -134,8 +136,8 @@ class TransactionContext { } if (logger.isInfoEnabled()) { - logger.info(String.format("%s transaction for test context %s.", - (this.flaggedForRollback ? "Rolled back" : "Committed"), this.testContext)); + logger.info((this.flaggedForRollback ? "Rolled back" : "Committed") + + " transaction for test: " + this.testContext); } } diff --git a/spring-test/src/main/java/org/springframework/test/context/transaction/TransactionContextHolder.java b/spring-test/src/main/java/org/springframework/test/context/transaction/TransactionContextHolder.java index 2932b50941..cea02a6365 100644 --- a/spring-test/src/main/java/org/springframework/test/context/transaction/TransactionContextHolder.java +++ b/spring-test/src/main/java/org/springframework/test/context/transaction/TransactionContextHolder.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2017 the original author or authors. + * Copyright 2002-2018 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. @@ -31,7 +31,7 @@ class TransactionContextHolder { new NamedInheritableThreadLocal<>("Test Transaction Context"); - static void setCurrentTransactionContext(@Nullable TransactionContext transactionContext) { + static void setCurrentTransactionContext(TransactionContext transactionContext) { currentTransactionContext.set(transactionContext); } @@ -42,11 +42,9 @@ class TransactionContextHolder { @Nullable static TransactionContext removeCurrentTransactionContext() { - synchronized (currentTransactionContext) { - TransactionContext transactionContext = currentTransactionContext.get(); - currentTransactionContext.remove(); - return transactionContext; - } + TransactionContext transactionContext = currentTransactionContext.get(); + currentTransactionContext.remove(); + return transactionContext; } } \ No newline at end of file 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 ec6c7a052e..b842f32ac9 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-2017 the original author or authors. + * Copyright 2002-2018 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. @@ -136,6 +136,7 @@ public class TransactionalTestExecutionListener extends AbstractTestExecutionLis // Do not require @Transactional test methods to be public. protected final TransactionAttributeSource attributeSource = new AnnotationTransactionAttributeSource(false); + /** * Returns {@code 4000}. */ @@ -159,10 +160,10 @@ public class TransactionalTestExecutionListener extends AbstractTestExecutionLis public void beforeTestMethod(final TestContext testContext) throws Exception { Method testMethod = testContext.getTestMethod(); Class testClass = testContext.getTestClass(); - Assert.notNull(testMethod, "The test method of the supplied TestContext must not be null"); + Assert.notNull(testMethod, "Test method of supplied TestContext must not be null"); TransactionContext txContext = TransactionContextHolder.removeCurrentTransactionContext(); - Assert.state(txContext == null, "Cannot start a new transaction without ending the existing transaction."); + Assert.state(txContext == null, "Cannot start new transaction without ending existing transaction"); PlatformTransactionManager tm = null; TransactionAttribute transactionAttribute = this.attributeSource.getTransactionAttribute(testMethod, testClass); @@ -172,8 +173,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) { @@ -181,9 +182,8 @@ public class TransactionalTestExecutionListener extends AbstractTestExecutionLis } tm = getTransactionManager(testContext, transactionAttribute.getQualifier()); - Assert.state(tm != null, () -> String.format( - "Failed to retrieve PlatformTransactionManager for @Transactional test for test context %s.", - testContext)); + Assert.state(tm != null, + () -> "Failed to retrieve PlatformTransactionManager for @Transactional test: " + testContext); } if (tm != null) { @@ -368,8 +368,8 @@ public class TransactionalTestExecutionListener extends AbstractTestExecutionLis if (rollbackPresent) { boolean defaultRollback = rollback.value(); if (logger.isDebugEnabled()) { - logger.debug(String.format("Retrieved default @Rollback(%s) for test class [%s].", defaultRollback, - testClass.getName())); + logger.debug(String.format("Retrieved default @Rollback(%s) for test class [%s].", + defaultRollback, testClass.getName())); } return defaultRollback; } diff --git a/spring-test/src/test/java/org/springframework/test/context/transaction/PrimaryTransactionManagerTests.java b/spring-test/src/test/java/org/springframework/test/context/transaction/PrimaryTransactionManagerTests.java index d9cd152679..dabedc902e 100644 --- a/spring-test/src/test/java/org/springframework/test/context/transaction/PrimaryTransactionManagerTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/transaction/PrimaryTransactionManagerTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2016 the original author or authors. + * Copyright 2002-2018 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. @@ -53,38 +53,6 @@ import static org.junit.Assert.*; @DirtiesContext public class PrimaryTransactionManagerTests { - @Configuration - static class Config { - - @Primary - @Bean - public PlatformTransactionManager primaryTransactionManager() { - return new DataSourceTransactionManager(dataSource1()); - } - - @Bean - public PlatformTransactionManager additionalTransactionManager() { - return new DataSourceTransactionManager(dataSource2()); - } - - @Bean - public DataSource dataSource1() { - // @formatter:off - return new EmbeddedDatabaseBuilder() - .generateUniqueName(true) - .addScript("classpath:/org/springframework/test/context/jdbc/schema.sql") - .build(); - // @formatter:on - } - - @Bean - public DataSource dataSource2() { - return new EmbeddedDatabaseBuilder().generateUniqueName(true).build(); - } - - } - - private JdbcTemplate jdbcTemplate; @@ -93,11 +61,17 @@ public class PrimaryTransactionManagerTests { this.jdbcTemplate = new JdbcTemplate(dataSource1); } + @BeforeTransaction public void beforeTransaction() { assertNumUsers(0); } + @AfterTransaction + public void afterTransaction() { + assertNumUsers(0); + } + @Test @Transactional public void transactionalTest() { @@ -109,14 +83,38 @@ public class PrimaryTransactionManagerTests { assertNumUsers(1); } - @AfterTransaction - public void afterTransaction() { - assertNumUsers(0); + private void assertNumUsers(int expected) { + assertEquals("Number of rows in the 'user' table", expected, + JdbcTestUtils.countRowsInTable(this.jdbcTemplate, "user")); } - private void assertNumUsers(int expected) { - assertEquals("Number of rows in the 'user' table.", expected, - JdbcTestUtils.countRowsInTable(this.jdbcTemplate, "user")); + + @Configuration + static class Config { + + @Primary + @Bean + public PlatformTransactionManager primaryTransactionManager() { + return new DataSourceTransactionManager(dataSource1()); + } + + @Bean + public PlatformTransactionManager additionalTransactionManager() { + return new DataSourceTransactionManager(dataSource2()); + } + + @Bean + public DataSource dataSource1() { + return new EmbeddedDatabaseBuilder() + .generateUniqueName(true) + .addScript("classpath:/org/springframework/test/context/jdbc/schema.sql") + .build(); + } + + @Bean + public DataSource dataSource2() { + return new EmbeddedDatabaseBuilder().generateUniqueName(true).build(); + } } } 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 8a67cec45e..c0b80116c9 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 @@ -23,8 +23,8 @@ import org.junit.After; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; - import org.mockito.BDDMockito; + import org.springframework.beans.BeanUtils; import org.springframework.core.annotation.AliasFor; import org.springframework.test.annotation.Commit; @@ -51,7 +51,6 @@ public class TransactionalTestExecutionListenerTests { private final PlatformTransactionManager tm = mock(PlatformTransactionManager.class); private final TransactionalTestExecutionListener listener = new TransactionalTestExecutionListener() { - @Override protected PlatformTransactionManager getTransactionManager(TestContext testContext, String qualifier) { return tm; @@ -64,100 +63,21 @@ public class TransactionalTestExecutionListenerTests { public ExpectedException exception = ExpectedException.none(); - private void assertBeforeTestMethod(Class clazz) throws Exception { - assertBeforeTestMethodWithTransactionalTestMethod(clazz); - assertBeforeTestMethodWithNonTransactionalTestMethod(clazz); - } - - private void assertBeforeTestMethodWithTransactionalTestMethod(Class clazz) throws Exception { - assertBeforeTestMethodWithTransactionalTestMethod(clazz, true); - } - - private void assertBeforeTestMethodWithTransactionalTestMethod(Class clazz, boolean invokedInTx) - throws Exception { - BDDMockito.> given(testContext.getTestClass()).willReturn(clazz); - Invocable instance = BeanUtils.instantiateClass(clazz); - given(testContext.getTestInstance()).willReturn(instance); - given(testContext.getTestMethod()).willReturn(clazz.getDeclaredMethod("transactionalTest")); - - assertFalse("callback should not have been invoked", instance.invoked()); - TransactionContextHolder.removeCurrentTransactionContext(); - listener.beforeTestMethod(testContext); - assertEquals(invokedInTx, instance.invoked()); - } - - private void assertBeforeTestMethodWithNonTransactionalTestMethod(Class clazz) - throws Exception { - BDDMockito.> given(testContext.getTestClass()).willReturn(clazz); - Invocable instance = BeanUtils.instantiateClass(clazz); - given(testContext.getTestInstance()).willReturn(instance); - given(testContext.getTestMethod()).willReturn(clazz.getDeclaredMethod("nonTransactionalTest")); - - assertFalse("callback should not have been invoked", instance.invoked()); - TransactionContextHolder.removeCurrentTransactionContext(); - listener.beforeTestMethod(testContext); - assertFalse("callback should not have been invoked", instance.invoked()); - } - - private void assertAfterTestMethod(Class clazz) throws Exception { - assertAfterTestMethodWithTransactionalTestMethod(clazz); - assertAfterTestMethodWithNonTransactionalTestMethod(clazz); - } - - private void assertAfterTestMethodWithTransactionalTestMethod(Class clazz) throws Exception { - BDDMockito.> given(testContext.getTestClass()).willReturn(clazz); - Invocable instance = BeanUtils.instantiateClass(clazz); - given(testContext.getTestInstance()).willReturn(instance); - given(testContext.getTestMethod()).willReturn(clazz.getDeclaredMethod("transactionalTest")); - - given(tm.getTransaction(BDDMockito.any(TransactionDefinition.class))).willReturn(new SimpleTransactionStatus()); - - assertFalse("callback should not have been invoked", instance.invoked()); - TransactionContextHolder.removeCurrentTransactionContext(); - listener.beforeTestMethod(testContext); - assertFalse("callback should not have been invoked", instance.invoked()); - listener.afterTestMethod(testContext); - assertTrue("callback should have been invoked", instance.invoked()); - } - - private void assertAfterTestMethodWithNonTransactionalTestMethod(Class clazz) throws Exception { - BDDMockito.> given(testContext.getTestClass()).willReturn(clazz); - Invocable instance = BeanUtils.instantiateClass(clazz); - given(testContext.getTestInstance()).willReturn(instance); - given(testContext.getTestMethod()).willReturn(clazz.getDeclaredMethod("nonTransactionalTest")); - - assertFalse("callback should not have been invoked", instance.invoked()); - TransactionContextHolder.removeCurrentTransactionContext(); - listener.beforeTestMethod(testContext); - listener.afterTestMethod(testContext); - assertFalse("callback should not have been invoked", instance.invoked()); - } - - private void assertIsRollback(Class clazz, boolean rollback) throws NoSuchMethodException, Exception { - BDDMockito.> given(testContext.getTestClass()).willReturn(clazz); - given(testContext.getTestMethod()).willReturn(clazz.getDeclaredMethod("test")); - assertEquals(rollback, listener.isRollback(testContext)); - } - @After public void cleanUpThreadLocalStateForSubsequentTestClassesInSuite() { TransactionContextHolder.removeCurrentTransactionContext(); } - /** - * SPR-13895 - */ - @Test + + @Test // SPR-13895 public void transactionalTestWithoutTransactionManager() throws Exception { TransactionalTestExecutionListener listener = new TransactionalTestExecutionListener() { - protected PlatformTransactionManager getTransactionManager(TestContext testContext, String qualifier) { return null; } }; Class clazz = TransactionalDeclaredOnClassLocallyTestCase.class; - BDDMockito.> given(testContext.getTestClass()).willReturn(clazz); Invocable instance = BeanUtils.instantiateClass(clazz); given(testContext.getTestInstance()).willReturn(instance); @@ -172,7 +92,7 @@ public class TransactionalTestExecutionListenerTests { } catch (IllegalStateException e) { assertTrue(e.getMessage().startsWith( - "Failed to retrieve PlatformTransactionManager for @Transactional test for test context")); + "Failed to retrieve PlatformTransactionManager for @Transactional test")); } } @@ -191,7 +111,7 @@ public class TransactionalTestExecutionListenerTests { // Note: not actually invoked within a transaction since the test class is // annotated with @MetaTxWithOverride(propagation = NOT_SUPPORTED) assertBeforeTestMethodWithTransactionalTestMethod( - TransactionalDeclaredOnClassViaMetaAnnotationWithOverrideTestCase.class, false); + TransactionalDeclaredOnClassViaMetaAnnotationWithOverrideTestCase.class, false); } @Test @@ -199,7 +119,7 @@ public class TransactionalTestExecutionListenerTests { // Note: not actually invoked within a transaction since the method is // annotated with @MetaTxWithOverride(propagation = NOT_SUPPORTED) assertBeforeTestMethodWithTransactionalTestMethod( - TransactionalDeclaredOnMethodViaMetaAnnotationWithOverrideTestCase.class, false); + TransactionalDeclaredOnMethodViaMetaAnnotationWithOverrideTestCase.class, false); assertBeforeTestMethodWithNonTransactionalTestMethod(TransactionalDeclaredOnMethodViaMetaAnnotationWithOverrideTestCase.class); } @@ -289,11 +209,84 @@ public class TransactionalTestExecutionListenerTests { } - // ------------------------------------------------------------------------- + private void assertBeforeTestMethod(Class clazz) throws Exception { + assertBeforeTestMethodWithTransactionalTestMethod(clazz); + assertBeforeTestMethodWithNonTransactionalTestMethod(clazz); + } + + private void assertBeforeTestMethodWithTransactionalTestMethod(Class clazz) throws Exception { + assertBeforeTestMethodWithTransactionalTestMethod(clazz, true); + } + + private void assertBeforeTestMethodWithTransactionalTestMethod(Class clazz, boolean invokedInTx) + throws Exception { + + BDDMockito.> given(testContext.getTestClass()).willReturn(clazz); + Invocable instance = BeanUtils.instantiateClass(clazz); + given(testContext.getTestInstance()).willReturn(instance); + given(testContext.getTestMethod()).willReturn(clazz.getDeclaredMethod("transactionalTest")); + + assertFalse("callback should not have been invoked", instance.invoked()); + TransactionContextHolder.removeCurrentTransactionContext(); + listener.beforeTestMethod(testContext); + assertEquals(invokedInTx, instance.invoked()); + } + + private void assertBeforeTestMethodWithNonTransactionalTestMethod(Class clazz) throws Exception { + BDDMockito.> given(testContext.getTestClass()).willReturn(clazz); + Invocable instance = BeanUtils.instantiateClass(clazz); + given(testContext.getTestInstance()).willReturn(instance); + given(testContext.getTestMethod()).willReturn(clazz.getDeclaredMethod("nonTransactionalTest")); + + assertFalse("callback should not have been invoked", instance.invoked()); + TransactionContextHolder.removeCurrentTransactionContext(); + listener.beforeTestMethod(testContext); + assertFalse("callback should not have been invoked", instance.invoked()); + } + + private void assertAfterTestMethod(Class clazz) throws Exception { + assertAfterTestMethodWithTransactionalTestMethod(clazz); + assertAfterTestMethodWithNonTransactionalTestMethod(clazz); + } + + private void assertAfterTestMethodWithTransactionalTestMethod(Class clazz) throws Exception { + BDDMockito.> given(testContext.getTestClass()).willReturn(clazz); + Invocable instance = BeanUtils.instantiateClass(clazz); + given(testContext.getTestInstance()).willReturn(instance); + given(testContext.getTestMethod()).willReturn(clazz.getDeclaredMethod("transactionalTest")); + given(tm.getTransaction(BDDMockito.any(TransactionDefinition.class))).willReturn(new SimpleTransactionStatus()); + + assertFalse("callback should not have been invoked", instance.invoked()); + TransactionContextHolder.removeCurrentTransactionContext(); + listener.beforeTestMethod(testContext); + assertFalse("callback should not have been invoked", instance.invoked()); + listener.afterTestMethod(testContext); + assertTrue("callback should have been invoked", instance.invoked()); + } + + private void assertAfterTestMethodWithNonTransactionalTestMethod(Class clazz) throws Exception { + BDDMockito.> given(testContext.getTestClass()).willReturn(clazz); + Invocable instance = BeanUtils.instantiateClass(clazz); + given(testContext.getTestInstance()).willReturn(instance); + given(testContext.getTestMethod()).willReturn(clazz.getDeclaredMethod("nonTransactionalTest")); + + assertFalse("callback should not have been invoked", instance.invoked()); + TransactionContextHolder.removeCurrentTransactionContext(); + listener.beforeTestMethod(testContext); + listener.afterTestMethod(testContext); + assertFalse("callback should not have been invoked", instance.invoked()); + } + + private void assertIsRollback(Class clazz, boolean rollback) throws Exception { + BDDMockito.> given(testContext.getTestClass()).willReturn(clazz); + given(testContext.getTestMethod()).willReturn(clazz.getDeclaredMethod("test")); + assertEquals(rollback, listener.isRollback(testContext)); + } + @Transactional @Retention(RetentionPolicy.RUNTIME) - private static @interface MetaTransactional { + private @interface MetaTransactional { } @Transactional @@ -308,12 +301,12 @@ public class TransactionalTestExecutionListenerTests { @BeforeTransaction @Retention(RetentionPolicy.RUNTIME) - private static @interface MetaBeforeTransaction { + private @interface MetaBeforeTransaction { } @AfterTransaction @Retention(RetentionPolicy.RUNTIME) - private static @interface MetaAfterTransaction { + private @interface MetaAfterTransaction { } private interface Invocable { @@ -348,7 +341,6 @@ public class TransactionalTestExecutionListenerTests { } public void transactionalTest() { - /* no-op */ } } @@ -361,11 +353,9 @@ public class TransactionalTestExecutionListenerTests { @Transactional public void transactionalTest() { - /* no-op */ } public void nonTransactionalTest() { - /* no-op */ } } @@ -378,7 +368,6 @@ public class TransactionalTestExecutionListenerTests { } public void transactionalTest() { - /* no-op */ } } @@ -391,11 +380,9 @@ public class TransactionalTestExecutionListenerTests { @MetaTransactional public void transactionalTest() { - /* no-op */ } public void nonTransactionalTest() { - /* no-op */ } } @@ -408,7 +395,6 @@ public class TransactionalTestExecutionListenerTests { } public void transactionalTest() { - /* no-op */ } } @@ -421,11 +407,9 @@ public class TransactionalTestExecutionListenerTests { @MetaTxWithOverride(propagation = NOT_SUPPORTED) public void transactionalTest() { - /* no-op */ } public void nonTransactionalTest() { - /* no-op */ } } @@ -438,11 +422,9 @@ public class TransactionalTestExecutionListenerTests { @Transactional public void transactionalTest() { - /* no-op */ } public void nonTransactionalTest() { - /* no-op */ } } @@ -455,11 +437,9 @@ public class TransactionalTestExecutionListenerTests { @Transactional public void transactionalTest() { - /* no-op */ } public void nonTransactionalTest() { - /* no-op */ } } @@ -472,11 +452,9 @@ public class TransactionalTestExecutionListenerTests { @Transactional public void transactionalTest() { - /* no-op */ } public void nonTransactionalTest() { - /* no-op */ } } @@ -489,11 +467,9 @@ public class TransactionalTestExecutionListenerTests { @Transactional public void transactionalTest() { - /* no-op */ } public void nonTransactionalTest() { - /* no-op */ } } @@ -518,11 +494,9 @@ public class TransactionalTestExecutionListenerTests { @Transactional public void transactionalTest() { - /* no-op */ } public void nonTransactionalTest() { - /* no-op */ } } @@ -531,11 +505,9 @@ public class TransactionalTestExecutionListenerTests { @Transactional public void transactionalTest() { - /* no-op */ } public void nonTransactionalTest() { - /* no-op */ } }