Make AppEngineRule work with JUnit 5 (#548)

* Make AppEngineRule work with JUnit 5

Made AppEngineRule work with both JUnit4 and JUnit 5 and applied
it to PremiumListTest.

Next step is to convert SqlIntegrationTestSuite.java to JUnit5.
This commit is contained in:
Weimin Yu 2020-04-07 14:59:25 -04:00 committed by GitHub
parent 2f6ba8cc29
commit 3dbc4e5d46
4 changed files with 97 additions and 15 deletions

View file

@ -29,16 +29,14 @@ import google.registry.model.registry.label.PremiumList.PremiumListEntry;
import google.registry.model.registry.label.PremiumList.PremiumListRevision;
import google.registry.testing.AppEngineRule;
import org.joda.money.Money;
import org.junit.Rule;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.migrationsupport.rules.EnableRuleMigrationSupport;
import org.junit.jupiter.api.extension.RegisterExtension;
/** Unit tests for {@link PremiumList}. */
@EnableRuleMigrationSupport
public class PremiumListTest {
@Rule
@RegisterExtension
public final AppEngineRule appEngine = AppEngineRule.builder().withDatastoreAndCloudSql().build();
@BeforeEach

View file

@ -27,6 +27,7 @@ import java.sql.SQLException;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Supplier;
import javax.persistence.Entity;
import org.junit.rules.ExternalResource;
@ -51,15 +52,15 @@ public class JpaEntityCoverage extends ExternalResource {
private static final Map<String, Boolean> testsJpaEntities = Maps.newHashMap();
// Provides class name of the test being executed.
private final TestCaseWatcher watcher;
private final Supplier<String> currTestClassNameSupplier;
public JpaEntityCoverage(TestCaseWatcher watcher) {
this.watcher = watcher;
public JpaEntityCoverage(Supplier<String> currTestClassNameSupplier) {
this.currTestClassNameSupplier = currTestClassNameSupplier;
}
@Override
public void before() {
testsJpaEntities.putIfAbsent(watcher.getTestClass(), false);
testsJpaEntities.putIfAbsent(currTestClassNameSupplier.get(), false);
}
@Override
@ -69,7 +70,7 @@ public class JpaEntityCoverage extends ExternalResource {
.forEach(
entity -> {
allCoveredJpaEntities.add(entity);
testsJpaEntities.put(watcher.getTestClass(), true);
testsJpaEntities.put(currTestClassNameSupplier.get(), true);
});
}

View file

@ -29,6 +29,9 @@ import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import org.joda.time.DateTime;
import org.junit.jupiter.api.extension.AfterEachCallback;
import org.junit.jupiter.api.extension.BeforeEachCallback;
import org.junit.jupiter.api.extension.ExtensionContext;
import org.junit.rules.RuleChain;
import org.junit.rules.TestRule;
import org.junit.runner.Description;
@ -88,7 +91,7 @@ public class JpaTestRules {
this.ruleChain =
RuleChain.outerRule(watcher)
.around(integrationTestRule)
.around(new JpaEntityCoverage(watcher));
.around(new JpaEntityCoverage(watcher::getTestClass));
}
@Override
@ -97,6 +100,40 @@ public class JpaTestRules {
}
}
/**
* JUnit extension for member classes of {@link
* google.registry.schema.integration.SqlIntegrationTestSuite}. In addition to providing a
* database through {@link JpaIntegrationTestRule}, it also keeps track of the test coverage of
* the declared JPA entities (in persistence.xml). Per-class statistics are stored in static
* variables. The SqlIntegrationTestSuite inspects the cumulative statistics after all test
* classes have run.
*/
public static final class JpaIntegrationWithCoverageExtension
implements BeforeEachCallback, AfterEachCallback {
private String currentTestClassName = null;
private final JpaEntityCoverage jpaEntityCoverage =
new JpaEntityCoverage(() -> this.currentTestClassName);
private final JpaIntegrationTestRule integrationTestRule;
JpaIntegrationWithCoverageExtension(JpaIntegrationTestRule integrationTestRule) {
this.integrationTestRule = integrationTestRule;
}
@Override
public void beforeEach(ExtensionContext context) throws Exception {
this.currentTestClassName = context.getRequiredTestClass().getName();
integrationTestRule.before();
jpaEntityCoverage.before();
}
@Override
public void afterEach(ExtensionContext context) throws Exception {
jpaEntityCoverage.after();
integrationTestRule.after();
this.currentTestClassName = null;
}
}
/** Builder of test rules that provide {@link JpaTransactionManager}. */
public static class Builder {
private String initScript;
@ -149,6 +186,15 @@ public class JpaTestRules {
return new JpaIntegrationWithCoverageRule(buildIntegrationTestRule());
}
/**
* JUnit extension that adapts {@link JpaIntegrationTestRule} for JUnit 5 and also checks test
* coverage of JPA entity classes.
*/
public JpaIntegrationWithCoverageExtension buildIntegrationWithCoverageExtension() {
checkState(initScript == null, "Integration tests do not accept initScript");
return new JpaIntegrationWithCoverageExtension(buildIntegrationTestRule());
}
/** Builds a {@link JpaUnitTestRule} instance. */
public JpaUnitTestRule buildUnitTestRule() {
checkState(

View file

@ -14,6 +14,7 @@
package google.registry.testing;
import static com.google.common.base.Preconditions.checkState;
import static com.google.common.truth.Truth.assertWithMessage;
import static google.registry.testing.DatastoreHelper.persistSimpleResources;
import static google.registry.util.ResourceUtils.readResourceUtf8;
@ -40,6 +41,7 @@ import google.registry.model.registrar.Registrar.State;
import google.registry.model.registrar.RegistrarAddress;
import google.registry.model.registrar.RegistrarContact;
import google.registry.persistence.transaction.JpaTestRules;
import google.registry.persistence.transaction.JpaTestRules.JpaIntegrationWithCoverageExtension;
import google.registry.persistence.transaction.JpaTestRules.JpaIntegrationWithCoverageRule;
import google.registry.util.Clock;
import java.io.ByteArrayInputStream;
@ -53,6 +55,9 @@ import javax.annotation.Nullable;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import org.junit.jupiter.api.extension.AfterEachCallback;
import org.junit.jupiter.api.extension.BeforeEachCallback;
import org.junit.jupiter.api.extension.ExtensionContext;
import org.junit.rules.ExternalResource;
import org.junit.rules.TemporaryFolder;
import org.junit.runner.Description;
@ -66,9 +71,15 @@ import org.junit.runners.model.Statement;
*
* <p>This rule also resets global Objectify for the current thread.
*
* <p>This class works with both JUnit 4 and JUnit 5. With JUnit 4, the test runner calls {@link
* #apply(Statement, Description)}, which in turns calls {@link #before()} on entry and {@link
* #after()} on exit. With JUnit 5, the test runner calls {@link #beforeEach(ExtensionContext)} and
* {@link #afterEach(ExtensionContext)}.
*
* @see org.junit.rules.ExternalResource
*/
public final class AppEngineRule extends ExternalResource {
public final class AppEngineRule extends ExternalResource
implements BeforeEachCallback, AfterEachCallback {
public static final String NEW_REGISTRAR_GAE_USER_ID = "666";
public static final String THE_REGISTRAR_GAE_USER_ID = "31337";
@ -93,6 +104,8 @@ public final class AppEngineRule extends ExternalResource {
/** A rule-within-a-rule to provide a temporary folder for AppEngineRule's internal temp files. */
TemporaryFolder temporaryFolder = new TemporaryFolder();
// Sets up a SQL database when running on JUnit 5.
JpaIntegrationWithCoverageExtension jpaIntegrationWithCoverageExtension = null;
private boolean withDatastoreAndCloudSql;
private boolean withLocalModules;
@ -257,14 +270,38 @@ public final class AppEngineRule extends ExternalResource {
.build();
}
/** Called before every test method. JUnit 5 only. */
@Override
public void beforeEach(ExtensionContext context) throws Exception {
before();
if (withDatastoreAndCloudSql) {
JpaTestRules.Builder builder = new JpaTestRules.Builder();
if (clock != null) {
builder.withClock(clock);
}
jpaIntegrationWithCoverageExtension = builder.buildIntegrationWithCoverageExtension();
jpaIntegrationWithCoverageExtension.beforeEach(context);
}
}
/** Called after each test method. JUnit 5 only. */
@Override
public void afterEach(ExtensionContext context) throws Exception {
if (withDatastoreAndCloudSql) {
checkState(
jpaIntegrationWithCoverageExtension != null, "Null jpaIntegrationWithCoverageExtension");
jpaIntegrationWithCoverageExtension.afterEach(context);
}
after();
}
/**
* Hack to make sure AppEngineRule is always wrapped in a {@link JpaIntegrationWithCoverageRule}.
* JUnit 4 only.
*/
// Note: Even with @EnableRuleMigrationSupport, JUnit5 runner does not call this method.
// Note 2: Do not migrate any members of SqlIntegrationTestSuite to JUnit5 before
// calls to JpaIntegrationWithCoverageRule can be made elsewhere.
// TODO(weiminyu): make JpaIntegrationWithCoverageRule implement ExternaResource and invoke it in
// before() and after(), then drop this method.
// Note 2: Do not migrate members of SqlIntegrationTestSuite to JUnit5 individually.
// TODO(weiminyu): migrate SqlIntegrationTestSuite in one go.
@Override
public Statement apply(Statement base, Description description) {
Statement statement = base;