Hack to call setup and teardown in JUnit5 suite (#560)

* Hack to call setup and teardown in JUnit5 suite

JUnit 5 runner does not support @BeforeAll and @AfterAll declared
in the Suite class (as opposed to the member classes). However,
staying with the JUnit 4 suite runner would prevent any member
classes from migrating to JUnit 5.

We use a hack to invoke suite-level set up and teardown from tests.
This change is safe in that if the JUnit 5 runner implementation changes
behavior, we will only see false alarms.
This commit is contained in:
Weimin Yu 2020-04-16 14:46:08 -04:00 committed by GitHub
parent 9db4d1a082
commit 9b47a6cfee
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 59 additions and 11 deletions

View file

@ -862,6 +862,8 @@ task standardTest(type: FilteringTest) {
includeAllTests() includeAllTests()
exclude fragileTestPatterns exclude fragileTestPatterns
exclude outcastTestPatterns exclude outcastTestPatterns
// See SqlIntegrationTestSuite.java
exclude '**/*BeforeSuiteTest.*', '**/*AfterSuiteTest.*'
if (rootProject.findProperty("skipDockerIncompatibleTests") == "true") { if (rootProject.findProperty("skipDockerIncompatibleTests") == "true") {
exclude dockerIncompatibleTestPatterns exclude dockerIncompatibleTestPatterns

View file

@ -14,19 +14,22 @@
package google.registry.schema.integration; package google.registry.schema.integration;
import com.google.common.truth.Expect; import static com.google.common.truth.Truth.assert_;
import google.registry.model.domain.DomainBaseSqlTest; import google.registry.model.domain.DomainBaseSqlTest;
import google.registry.model.registry.RegistryLockDaoTest; import google.registry.model.registry.RegistryLockDaoTest;
import google.registry.persistence.transaction.JpaEntityCoverage; import google.registry.persistence.transaction.JpaEntityCoverage;
import google.registry.schema.cursor.CursorDaoTest; import google.registry.schema.cursor.CursorDaoTest;
import google.registry.schema.integration.SqlIntegrationTestSuite.AfterSuiteTest;
import google.registry.schema.integration.SqlIntegrationTestSuite.BeforeSuiteTest;
import google.registry.schema.registrar.RegistrarDaoTest; import google.registry.schema.registrar.RegistrarDaoTest;
import google.registry.schema.server.LockDaoTest; import google.registry.schema.server.LockDaoTest;
import google.registry.schema.tld.PremiumListDaoTest; import google.registry.schema.tld.PremiumListDaoTest;
import google.registry.schema.tld.ReservedListDaoTest; import google.registry.schema.tld.ReservedListDaoTest;
import google.registry.schema.tmch.ClaimsListDaoTest; import google.registry.schema.tmch.ClaimsListDaoTest;
import org.junit.AfterClass; import org.junit.jupiter.api.AfterAll;
import org.junit.BeforeClass; import org.junit.jupiter.api.BeforeAll;
import org.junit.ClassRule; import org.junit.jupiter.api.Test;
import org.junit.platform.runner.JUnitPlatform; import org.junit.platform.runner.JUnitPlatform;
import org.junit.platform.suite.api.SelectClasses; import org.junit.platform.suite.api.SelectClasses;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
@ -47,9 +50,23 @@ import org.junit.runner.RunWith;
* <p>Note that with {@code JpaIntegrationWithCoverageRule}, each method starts with an empty * <p>Note that with {@code JpaIntegrationWithCoverageRule}, each method starts with an empty
* database. Therefore this is not the right place for verifying backward data compatibility in * database. Therefore this is not the right place for verifying backward data compatibility in
* end-to-end functional tests. * end-to-end functional tests.
*
* <p>As of April 2020, none of the before/after annotations ({@code BeforeClass} and {@code
* AfterClass} in JUnit 4, or {@code BeforeAll} and {@code AfterAll} in JUnit5) work in a test suite
* run with {@link JUnitPlatform the current JUnit 5 runner}. However, staying with the JUnit 4
* runner would prevent any member tests from migrating to JUnit 5.
*
* <p>This class uses a hack to work with the current JUnit 5 runner. {@link BeforeSuiteTest} is
* added to the front of the suite class list and invokes the suite's setup method, and {@link
* AfterSuiteTest} is added to the tail of the suite class list and invokes the suite's teardown
* method. This works because the member tests are run in the order they are declared (See {@code
* org.junit.platform.engine.support.descriptor.AbstractTestDescriptor#addChild}). Should the
* ordering changes in the future, we will only get false alarms.
*/ */
@RunWith(JUnitPlatform.class) @RunWith(JUnitPlatform.class)
@SelectClasses({ @SelectClasses({
// BeforeSuiteTest must be the first entry. See class javadoc for details.
BeforeSuiteTest.class,
ClaimsListDaoTest.class, ClaimsListDaoTest.class,
CursorDaoTest.class, CursorDaoTest.class,
DomainBaseSqlTest.class, DomainBaseSqlTest.class,
@ -57,27 +74,56 @@ import org.junit.runner.RunWith;
PremiumListDaoTest.class, PremiumListDaoTest.class,
RegistrarDaoTest.class, RegistrarDaoTest.class,
RegistryLockDaoTest.class, RegistryLockDaoTest.class,
ReservedListDaoTest.class ReservedListDaoTest.class,
// AfterSuiteTest must be the last entry. See class javadoc for details.
AfterSuiteTest.class
}) })
public class SqlIntegrationTestSuite { public class SqlIntegrationTestSuite {
@ClassRule public static final Expect expect = Expect.create(); @BeforeAll // Not yet supported in JUnit 5. Called through BeforeSuiteTest.
@BeforeClass
public static void initJpaEntityCoverage() { public static void initJpaEntityCoverage() {
JpaEntityCoverage.init(); JpaEntityCoverage.init();
} }
@AfterClass @AfterAll // Not yet supported in JUnit 5. Called through AfterSuiteTest.
public static void checkJpaEntityCoverage() { public static void checkJpaEntityCoverage() {
expect // TODO(weiminyu): collect both assertion errors like Truth's Expect does in JUnit 4.
assert_()
.withMessage("Tests are missing for the following JPA entities:") .withMessage("Tests are missing for the following JPA entities:")
.that(JpaEntityCoverage.getUncoveredEntities()) .that(JpaEntityCoverage.getUncoveredEntities())
.isEmpty(); .isEmpty();
expect assert_()
.withMessage( .withMessage(
"The following classes do not test JPA entities. Please remove them from this suite") "The following classes do not test JPA entities. Please remove them from this suite")
.that(JpaEntityCoverage.getIrrelevantTestClasses()) .that(JpaEntityCoverage.getIrrelevantTestClasses())
.isEmpty(); .isEmpty();
} }
/**
* Hack for calling {@link SqlIntegrationTestSuite#initJpaEntityCoverage()} before all real tests
* in suite. See outer class javadoc for details.
*
* <p>The 'Test' suffix in class name is required.
*/
static class BeforeSuiteTest {
@Test
void beforeAll() {
initJpaEntityCoverage();
}
}
/**
* Hack for invoking {@link SqlIntegrationTestSuite#checkJpaEntityCoverage()} after all real tests
* in suite. See outer class javadoc for details.
*
* <p>The 'Test' suffix in class name is required.
*/
static class AfterSuiteTest {
@Test
void afterSuite() {
checkJpaEntityCoverage();
}
}
} }