diff --git a/extensions/narayana-jta/runtime/src/main/java/io/quarkus/narayana/jta/runtime/interceptor/TestTransactionInterceptor.java b/extensions/narayana-jta/runtime/src/main/java/io/quarkus/narayana/jta/runtime/interceptor/TestTransactionInterceptor.java index 4e3b0f770b5db..3e1b4a6486803 100644 --- a/extensions/narayana-jta/runtime/src/main/java/io/quarkus/narayana/jta/runtime/interceptor/TestTransactionInterceptor.java +++ b/extensions/narayana-jta/runtime/src/main/java/io/quarkus/narayana/jta/runtime/interceptor/TestTransactionInterceptor.java @@ -7,6 +7,7 @@ import javax.inject.Inject; import javax.interceptor.AroundInvoke; import javax.interceptor.InvocationContext; +import javax.transaction.Status; import javax.transaction.UserTransaction; import io.quarkus.narayana.jta.runtime.test.TestTransactionCallback; @@ -28,6 +29,14 @@ public class TestTransactionInterceptor { @AroundInvoke public Object intercept(InvocationContext context) throws Exception { + // do nothing in case there is already a transaction (e.g. self-intercepted non-private non-test method in test class) + // w/o this check userTransaction.begin() would fail because there is already a tx associated with the current thread + if (userTransaction.getStatus() != Status.STATUS_NO_TRANSACTION) { + return context.proceed(); + } + + // an exception from proceed() has to be captured to avoid shadowing it in finally() with a exception from rollback() + Throwable caught = null; try { userTransaction.begin(); for (TestTransactionCallback i : CALLBACKS) { @@ -38,9 +47,19 @@ public Object intercept(InvocationContext context) throws Exception { i.preRollback(); } return result; + } catch (Exception | Error e) { // note: "Error" shall mainly address AssertionError + caught = e; + throw e; } finally { - userTransaction.rollback(); + if (caught == null) { + userTransaction.rollback(); + } else { + try { + userTransaction.rollback(); + } catch (Exception e) { + caught.addSuppressed(e); + } + } } } - } diff --git a/integration-tests/hibernate-orm-panache/src/test/java/io/quarkus/it/panache/TestTransactionOnSingleMethodTest.java b/integration-tests/hibernate-orm-panache/src/test/java/io/quarkus/it/panache/TestTransactionOnSingleMethodTest.java new file mode 100644 index 0000000000000..53e8fa750b390 --- /dev/null +++ b/integration-tests/hibernate-orm-panache/src/test/java/io/quarkus/it/panache/TestTransactionOnSingleMethodTest.java @@ -0,0 +1,44 @@ +package io.quarkus.it.panache; + +import javax.transaction.Transactional; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.MethodOrderer.MethodName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestMethodOrder; + +import io.quarkus.test.TestTransaction; +import io.quarkus.test.junit.QuarkusTest; + +/** + * Tests that @TestTransaction works as expected when only used for a single test method + */ +@QuarkusTest +@TestMethodOrder(MethodName.class) +public class TestTransactionOnSingleMethodTest { + + @Test + @TestTransaction + public void test1() { + Assertions.assertEquals(0, Beer.find("name", "Lager").count()); + Beer b = new Beer(); + b.name = "Lager"; + Beer.persist(b); + } + + @Test + @Transactional + public void test2() { + Assertions.assertEquals(0, Beer.find("name", "Lager").count()); + Beer b = new Beer(); + b.name = "Lager"; + Beer.persist(b); + } + + @Test + @Transactional + public void test3() { + Assertions.assertEquals(1, Beer.find("name", "Lager").count()); + Beer.deleteAll(); + } +} diff --git a/integration-tests/hibernate-orm-panache/src/test/java/io/quarkus/it/panache/TestTransactionTest.java b/integration-tests/hibernate-orm-panache/src/test/java/io/quarkus/it/panache/TestTransactionTest.java index 892c9168979a3..4da5e4850b92b 100644 --- a/integration-tests/hibernate-orm-panache/src/test/java/io/quarkus/it/panache/TestTransactionTest.java +++ b/integration-tests/hibernate-orm-panache/src/test/java/io/quarkus/it/panache/TestTransactionTest.java @@ -1,20 +1,22 @@ package io.quarkus.it.panache; import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.MethodOrderer.MethodName; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestMethodOrder; import io.quarkus.test.TestTransaction; import io.quarkus.test.junit.QuarkusTest; /** - * Tests that @TestTransaction works as expected + * Tests that @TestTransaction works as expected when used for the entire class */ @QuarkusTest @TestTransaction +@TestMethodOrder(MethodName.class) public class TestTransactionTest { @Test - @TestTransaction public void test1() { Assertions.assertEquals(0, Beer.find("name", "Lager").count()); Beer b = new Beer(); @@ -23,11 +25,20 @@ public void test1() { } @Test - @TestTransaction public void test2() { Assertions.assertEquals(0, Beer.find("name", "Lager").count()); + // interceptor must not choke on this self-intercepted non-test method invocation + intentionallyNonPrivateHelperMethod(); Beer b = new Beer(); b.name = "Lager"; Beer.persist(b); } + + @Test + public void test3() { + Assertions.assertEquals(0, Beer.find("name", "Lager").count()); + } + + void intentionallyNonPrivateHelperMethod() { + } }