diff --git a/src/main/java/io/ebean/ForTests.java b/src/main/java/io/ebean/ForTests.java new file mode 100644 index 000000000..d1b3d89b2 --- /dev/null +++ b/src/main/java/io/ebean/ForTests.java @@ -0,0 +1,113 @@ +package io.ebean; + +import io.ebeaninternal.api.HelpScopeTrans; + +/** + * Helper methods for testing. + */ +public class ForTests { + + /** + * Enable or disable @Transactional methods. + *

+ * This is intended for testing purposes such that tests + * on code with {@code @Transactional} methods don't actually + * start or complete transactions. + *

+ * + * @param enable Set false to disable {@code @Transactional} methods + */ + public static void enableTransactional(boolean enable) { + HelpScopeTrans.setEnabled(enable); + } + + /** + * Run the closure with @Transactional methods + * effectively disabled (they won't create/commit transactions). + */ + public static void noTransactional(Runnable run) { + try { + enableTransactional(false); + run.run(); + } finally { + enableTransactional(true); + } + } + + /** + * All transactions started in the closure are effectively rolled back. + *

+ * This creates a wrapping transaction that uses {@link Transaction#setNestedUseSavepoint()}. + * All nested transactions are created as savepoints. On completion the wrapping + * transaction is rolled back. + *

+ * + * @param run Closure that runs such that all the transactions are rolled back. + */ + public static void rollbackAll(Runnable run) { + + try (Transaction transaction = DB.beginTransaction()) { + transaction.setNestedUseSavepoint(); + run.run(); + + transaction.rollback(); + } + } + + /** + * Create and return a RollbackAll which should be closed at the end of the test(s). + *

+ * In tests for @Before we create the rollbackAll and on + * @After we close() it effectively rolling + * back all changes made during test execution. + *

+ * + *
{@code
+   *
+   *   private ForTests.RollbackAll rollbackAll;
+   *
+   *   @Before
+   *   public void before() {
+   *     rollbackAll = ForTests.createRollbackAll();
+   *   }
+   *
+   *   @After
+   *   public void after() {
+   *     rollbackAll.close();
+   *   }
+   *
+   *   ... tests execute and everything is rolled back
+   *
+   *
+   * }
+ */ + public static RollbackAll createRollbackAll() { + + final Transaction transaction = DB.beginTransaction(); + transaction.setNestedUseSavepoint(); + return new RollbackAll(transaction); + } + + /** + * A wrapping transaction used in test code to rollback all changes. + *

+ * We must ensure that close() is called. + *

+ */ + public static class RollbackAll implements AutoCloseable { + + private final Transaction transaction; + + private RollbackAll(Transaction transaction) { + this.transaction = transaction; + } + + /** + * Rollback the wrapping transaction. + */ + @Override + public void close() { + transaction.rollback(); + } + } +} diff --git a/src/test/java/org/tests/ForTestsBeforeAfterTest.java b/src/test/java/org/tests/ForTestsBeforeAfterTest.java new file mode 100644 index 000000000..f1832b4be --- /dev/null +++ b/src/test/java/org/tests/ForTestsBeforeAfterTest.java @@ -0,0 +1,49 @@ +package org.tests; + +import io.ebean.DB; +import io.ebean.ForTests; +import io.ebean.annotation.Transactional; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.tests.basic.type.BSimpleWithGen; + +import static org.assertj.core.api.StrictAssertions.assertThat; + +public class ForTestsBeforeAfterTest { + + private ForTests.RollbackAll rollbackAll; + + @Before + public void before() { + rollbackAll = ForTests.createRollbackAll(); + } + + @After + public void after() { + rollbackAll.close(); + assertThat(getCount()).isEqualTo(0); + } + + + @Test + public void createRollbackAll() { + + doInsert(); + assertThat(getCount()).isEqualTo(1); + } + + private int getCount() { + return DB.find(BSimpleWithGen.class) + .where().eq("name", "ForTestsBeforeAfterTest") + .findCount(); + } + + @Transactional + private void doInsert() { + + BSimpleWithGen bean = new BSimpleWithGen("ForTestsBeforeAfterTest"); + DB.save(bean); + } + +} diff --git a/src/test/java/org/tests/ForTestsTest.java b/src/test/java/org/tests/ForTestsTest.java new file mode 100644 index 000000000..fb5052f82 --- /dev/null +++ b/src/test/java/org/tests/ForTestsTest.java @@ -0,0 +1,54 @@ +package org.tests; + +import io.ebean.DB; +import io.ebean.ForTests; +import io.ebean.Transaction; +import io.ebean.annotation.Transactional; +import org.junit.Test; +import org.tests.basic.type.BSimpleWithGen; + +import static org.assertj.core.api.Assertions.assertThat; + +public class ForTestsTest { + + @Test + public void noTransactional_expect_noTransactionEnterExitCalled() { + + ForTests.noTransactional(this::checkForTransactional); + } + + @Test + public void enableTransactional() { + + ForTests.enableTransactional(false); + + checkForTransactional(); + + ForTests.enableTransactional(true); + } + + @Transactional + private void checkForTransactional() { + + final Transaction transaction = DB.currentTransaction(); + assertThat(transaction).isNull(); + } + + @Test + public void rollbackTransactions() { + + DB.find(BSimpleWithGen.class).delete(); + + ForTests.rollbackAll(this::doInsert); + + final int count = DB.find(BSimpleWithGen.class).findCount(); + assertThat(count).isEqualTo(0); + } + + @Transactional + private void doInsert() { + + BSimpleWithGen bean = new BSimpleWithGen("doInsert"); + DB.save(bean); + } +}