/* * 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. * You may obtain a copy of the License at * * http://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.transaction; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import org.junit.After; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; import org.mockito.BDDMockito; import org.springframework.core.annotation.AliasFor; import org.springframework.test.annotation.Commit; import org.springframework.test.annotation.Rollback; import org.springframework.test.context.TestContext; import org.springframework.transaction.PlatformTransactionManager; import org.springframework.transaction.TransactionDefinition; import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.support.SimpleTransactionStatus; import static org.junit.Assert.*; import static org.mockito.BDDMockito.*; import static org.springframework.transaction.annotation.Propagation.*; /** * Unit tests for {@link TransactionalTestExecutionListener}. * * @author Sam Brannen * @since 4.0 */ 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; } }; private final TestContext testContext = mock(TestContext.class); @Rule public ExpectedException exception = ExpectedException.none(); private void assertBeforeTestMethod(Class<? extends Invocable> clazz) throws Exception { assertBeforeTestMethodWithTransactionalTestMethod(clazz); assertBeforeTestMethodWithNonTransactionalTestMethod(clazz); } private void assertBeforeTestMethodWithTransactionalTestMethod(Class<? extends Invocable> clazz) throws Exception { assertBeforeTestMethodWithTransactionalTestMethod(clazz, true); } private void assertBeforeTestMethodWithTransactionalTestMethod(Class<? extends Invocable> clazz, boolean invokedInTx) throws Exception { BDDMockito.<Class<?>> given(testContext.getTestClass()).willReturn(clazz); Invocable instance = clazz.newInstance(); 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<? extends Invocable> clazz) throws Exception { BDDMockito.<Class<?>> given(testContext.getTestClass()).willReturn(clazz); Invocable instance = clazz.newInstance(); 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<? extends Invocable> clazz) throws Exception { assertAfterTestMethodWithTransactionalTestMethod(clazz); assertAfterTestMethodWithNonTransactionalTestMethod(clazz); } private void assertAfterTestMethodWithTransactionalTestMethod(Class<? extends Invocable> clazz) throws Exception { BDDMockito.<Class<?>> given(testContext.getTestClass()).willReturn(clazz); Invocable instance = clazz.newInstance(); 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<? extends Invocable> clazz) throws Exception { BDDMockito.<Class<?>> given(testContext.getTestClass()).willReturn(clazz); Invocable instance = clazz.newInstance(); 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.<Class<?>> 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 public void transactionalTestWithoutTransactionManager() throws Exception { TransactionalTestExecutionListener listener = new TransactionalTestExecutionListener() { protected PlatformTransactionManager getTransactionManager(TestContext testContext, String qualifier) { return null; } }; Class<? extends Invocable> clazz = TransactionalDeclaredOnClassLocallyTestCase.class; BDDMockito.<Class<?>> given(testContext.getTestClass()).willReturn(clazz); Invocable instance = clazz.newInstance(); given(testContext.getTestInstance()).willReturn(instance); given(testContext.getTestMethod()).willReturn(clazz.getDeclaredMethod("transactionalTest")); assertFalse("callback should not have been invoked", instance.invoked()); TransactionContextHolder.removeCurrentTransactionContext(); try { listener.beforeTestMethod(testContext); fail("Should have thrown an IllegalStateException"); } catch (IllegalStateException e) { assertTrue(e.getMessage().startsWith( "Failed to retrieve PlatformTransactionManager for @Transactional test for test context")); } } @Test public void beforeTestMethodWithTransactionalDeclaredOnClassLocally() throws Exception { assertBeforeTestMethodWithTransactionalTestMethod(TransactionalDeclaredOnClassLocallyTestCase.class); } @Test public void beforeTestMethodWithTransactionalDeclaredOnClassViaMetaAnnotation() throws Exception { assertBeforeTestMethodWithTransactionalTestMethod(TransactionalDeclaredOnClassViaMetaAnnotationTestCase.class); } @Test public void beforeTestMethodWithTransactionalDeclaredOnClassViaMetaAnnotationWithOverride() throws Exception { // Note: not actually invoked within a transaction since the test class is // annotated with @MetaTxWithOverride(propagation = NOT_SUPPORTED) assertBeforeTestMethodWithTransactionalTestMethod( TransactionalDeclaredOnClassViaMetaAnnotationWithOverrideTestCase.class, false); } @Test public void beforeTestMethodWithTransactionalDeclaredOnMethodViaMetaAnnotationWithOverride() throws Exception { // Note: not actually invoked within a transaction since the method is // annotated with @MetaTxWithOverride(propagation = NOT_SUPPORTED) assertBeforeTestMethodWithTransactionalTestMethod( TransactionalDeclaredOnMethodViaMetaAnnotationWithOverrideTestCase.class, false); assertBeforeTestMethodWithNonTransactionalTestMethod(TransactionalDeclaredOnMethodViaMetaAnnotationWithOverrideTestCase.class); } @Test public void beforeTestMethodWithTransactionalDeclaredOnMethodLocally() throws Exception { assertBeforeTestMethod(TransactionalDeclaredOnMethodLocallyTestCase.class); } @Test public void beforeTestMethodWithTransactionalDeclaredOnMethodViaMetaAnnotation() throws Exception { assertBeforeTestMethod(TransactionalDeclaredOnMethodViaMetaAnnotationTestCase.class); } @Test public void beforeTestMethodWithBeforeTransactionDeclaredLocally() throws Exception { assertBeforeTestMethod(BeforeTransactionDeclaredLocallyTestCase.class); } @Test public void beforeTestMethodWithBeforeTransactionDeclaredViaMetaAnnotation() throws Exception { assertBeforeTestMethod(BeforeTransactionDeclaredViaMetaAnnotationTestCase.class); } @Test public void afterTestMethodWithAfterTransactionDeclaredLocally() throws Exception { assertAfterTestMethod(AfterTransactionDeclaredLocallyTestCase.class); } @Test public void afterTestMethodWithAfterTransactionDeclaredViaMetaAnnotation() throws Exception { assertAfterTestMethod(AfterTransactionDeclaredViaMetaAnnotationTestCase.class); } @Test public void beforeTestMethodWithBeforeTransactionDeclaredAsInterfaceDefaultMethod() throws Exception { assertBeforeTestMethod(BeforeTransactionDeclaredAsInterfaceDefaultMethodTestCase.class); } @Test public void afterTestMethodWithAfterTransactionDeclaredAsInterfaceDefaultMethod() throws Exception { assertAfterTestMethod(AfterTransactionDeclaredAsInterfaceDefaultMethodTestCase.class); } @Test public void isRollbackWithMissingRollback() throws Exception { assertIsRollback(MissingRollbackTestCase.class, true); } @Test public void isRollbackWithEmptyMethodLevelRollback() throws Exception { assertIsRollback(EmptyMethodLevelRollbackTestCase.class, true); } @Test public void isRollbackWithMethodLevelRollbackWithExplicitValue() throws Exception { assertIsRollback(MethodLevelRollbackWithExplicitValueTestCase.class, false); } @Test public void isRollbackWithMethodLevelRollbackViaMetaAnnotation() throws Exception { assertIsRollback(MethodLevelRollbackViaMetaAnnotationTestCase.class, false); } @Test public void isRollbackWithEmptyClassLevelRollback() throws Exception { assertIsRollback(EmptyClassLevelRollbackTestCase.class, true); } @Test public void isRollbackWithClassLevelRollbackWithExplicitValue() throws Exception { assertIsRollback(ClassLevelRollbackWithExplicitValueTestCase.class, false); } @Test public void isRollbackWithClassLevelRollbackViaMetaAnnotation() throws Exception { assertIsRollback(ClassLevelRollbackViaMetaAnnotationTestCase.class, false); } @Test public void isRollbackWithClassLevelRollbackWithExplicitValueOnTestInterface() throws Exception { assertIsRollback(ClassLevelRollbackWithExplicitValueOnTestInterfaceTestCase.class, false); } @Test public void isRollbackWithClassLevelRollbackViaMetaAnnotationOnTestInterface() throws Exception { assertIsRollback(ClassLevelRollbackViaMetaAnnotationOnTestInterfaceTestCase.class, false); } // ------------------------------------------------------------------------- @Transactional @Retention(RetentionPolicy.RUNTIME) private static @interface MetaTransactional { } @Transactional @Retention(RetentionPolicy.RUNTIME) private static @interface MetaTxWithOverride { @AliasFor(annotation = Transactional.class, attribute = "value") String transactionManager() default ""; Propagation propagation() default REQUIRED; } @BeforeTransaction @Retention(RetentionPolicy.RUNTIME) private static @interface MetaBeforeTransaction { } @AfterTransaction @Retention(RetentionPolicy.RUNTIME) private static @interface MetaAfterTransaction { } private interface Invocable { void invoked(boolean invoked); boolean invoked(); } private static class AbstractInvocable implements Invocable { boolean invoked = false; @Override public void invoked(boolean invoked) { this.invoked = invoked; } @Override public boolean invoked() { return this.invoked; } } @Transactional static class TransactionalDeclaredOnClassLocallyTestCase extends AbstractInvocable { @BeforeTransaction public void beforeTransaction() { invoked(true); } public void transactionalTest() { /* no-op */ } } static class TransactionalDeclaredOnMethodLocallyTestCase extends AbstractInvocable { @BeforeTransaction public void beforeTransaction() { invoked(true); } @Transactional public void transactionalTest() { /* no-op */ } public void nonTransactionalTest() { /* no-op */ } } @MetaTransactional static class TransactionalDeclaredOnClassViaMetaAnnotationTestCase extends AbstractInvocable { @BeforeTransaction public void beforeTransaction() { invoked(true); } public void transactionalTest() { /* no-op */ } } static class TransactionalDeclaredOnMethodViaMetaAnnotationTestCase extends AbstractInvocable { @BeforeTransaction public void beforeTransaction() { invoked(true); } @MetaTransactional public void transactionalTest() { /* no-op */ } public void nonTransactionalTest() { /* no-op */ } } @MetaTxWithOverride(propagation = NOT_SUPPORTED) static class TransactionalDeclaredOnClassViaMetaAnnotationWithOverrideTestCase extends AbstractInvocable { @BeforeTransaction public void beforeTransaction() { invoked(true); } public void transactionalTest() { /* no-op */ } } static class TransactionalDeclaredOnMethodViaMetaAnnotationWithOverrideTestCase extends AbstractInvocable { @BeforeTransaction public void beforeTransaction() { invoked(true); } @MetaTxWithOverride(propagation = NOT_SUPPORTED) public void transactionalTest() { /* no-op */ } public void nonTransactionalTest() { /* no-op */ } } static class BeforeTransactionDeclaredLocallyTestCase extends AbstractInvocable { @BeforeTransaction public void beforeTransaction() { invoked(true); } @Transactional public void transactionalTest() { /* no-op */ } public void nonTransactionalTest() { /* no-op */ } } static class BeforeTransactionDeclaredViaMetaAnnotationTestCase extends AbstractInvocable { @MetaBeforeTransaction public void beforeTransaction() { invoked(true); } @Transactional public void transactionalTest() { /* no-op */ } public void nonTransactionalTest() { /* no-op */ } } static class AfterTransactionDeclaredLocallyTestCase extends AbstractInvocable { @AfterTransaction public void afterTransaction() { invoked(true); } @Transactional public void transactionalTest() { /* no-op */ } public void nonTransactionalTest() { /* no-op */ } } static class AfterTransactionDeclaredViaMetaAnnotationTestCase extends AbstractInvocable { @MetaAfterTransaction public void afterTransaction() { invoked(true); } @Transactional public void transactionalTest() { /* no-op */ } public void nonTransactionalTest() { /* no-op */ } } interface BeforeTransactionInterface extends Invocable { @BeforeTransaction default void beforeTransaction() { invoked(true); } } interface AfterTransactionInterface extends Invocable { @AfterTransaction default void afterTransaction() { invoked(true); } } static class BeforeTransactionDeclaredAsInterfaceDefaultMethodTestCase extends AbstractInvocable implements BeforeTransactionInterface { @Transactional public void transactionalTest() { /* no-op */ } public void nonTransactionalTest() { /* no-op */ } } static class AfterTransactionDeclaredAsInterfaceDefaultMethodTestCase extends AbstractInvocable implements AfterTransactionInterface { @Transactional public void transactionalTest() { /* no-op */ } public void nonTransactionalTest() { /* no-op */ } } static class MissingRollbackTestCase { public void test() { } } static class EmptyMethodLevelRollbackTestCase { @Rollback public void test() { } } static class MethodLevelRollbackWithExplicitValueTestCase { @Rollback(false) public void test() { } } static class MethodLevelRollbackViaMetaAnnotationTestCase { @Commit public void test() { } } @Rollback static class EmptyClassLevelRollbackTestCase { public void test() { } } @Rollback(false) static class ClassLevelRollbackWithExplicitValueTestCase { public void test() { } } @Commit static class ClassLevelRollbackViaMetaAnnotationTestCase { public void test() { } } @Rollback(false) interface RollbackFalseTestInterface { } static class ClassLevelRollbackWithExplicitValueOnTestInterfaceTestCase implements RollbackFalseTestInterface { public void test() { } } @Commit interface RollbackFalseViaMetaAnnotationTestInterface { } static class ClassLevelRollbackViaMetaAnnotationOnTestInterfaceTestCase implements RollbackFalseViaMetaAnnotationTestInterface { public void test() { } } }