diff --git a/extensions/junit5/pom.xml b/extensions/junit5/pom.xml new file mode 100644 index 000000000..16739d711 --- /dev/null +++ b/extensions/junit5/pom.xml @@ -0,0 +1,55 @@ + + + 4.0.0 + + com.google.truth.extensions + truth-extensions-parent + HEAD-SNAPSHOT + + truth-junit5-extension + Truth Extension for JUnit Jupiter + + An extension for JUnit Jupiter to use soft assertions with Truth test assertion framework + + + + com.google.truth + truth + + + org.junit.jupiter + junit-jupiter-api + + + org.junit.jupiter + junit-jupiter-engine + test + + + org.junit.platform + junit-platform-testkit + test + + + org.opentest4j + opentest4j + + + + + + maven-javadoc-plugin + + + maven-compiler-plugin + + 1.8 + 1.8 + + + + + diff --git a/extensions/junit5/src/main/java/com/google/common/truth/junit5/Expect.java b/extensions/junit5/src/main/java/com/google/common/truth/junit5/Expect.java new file mode 100644 index 000000000..216812c99 --- /dev/null +++ b/extensions/junit5/src/main/java/com/google/common/truth/junit5/Expect.java @@ -0,0 +1,82 @@ +package com.google.common.truth.junit5; + +import com.google.common.truth.FailureStrategy; +import com.google.common.truth.StandardSubjectBuilder; +import org.junit.jupiter.api.extension.AfterEachCallback; +import org.junit.jupiter.api.extension.Extension; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.api.extension.ParameterContext; +import org.junit.jupiter.api.extension.ParameterResolver; +import org.opentest4j.MultipleFailuresError; + +import java.util.ArrayList; +import java.util.List; + +/** + * An Extension that provides a {@link StandardSubjectBuilder} parameter to test methods. + * + *

Assertion failures on the given StandardSubjectBuilder will not immediately fail; + * instead, failures will be reported at the end of the test. + * + *

Usage: + * + *

+ * @ExtendWith(Expect.class) // or system property junit.jupiter.extensions.autodetection.enabled=true
+ * ...
+ *     @Test
+ *     public void test(StandardSubjectBuilder expect) {
+ *         ...
+ *     }
+ * 
+ */ +public class Expect implements Extension, ParameterResolver, AfterEachCallback { + @Override + public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext) { + return parameterContext.getParameter().getType() == StandardSubjectBuilder.class; + } + + @Override + public StandardSubjectBuilder resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) { + ExtensionContext.Store store = extensionContext.getStore(ExtensionContext.Namespace.create(Expect.class)); + return store.getOrComputeIfAbsent(extensionContext.getUniqueId(), key -> new Holder(), Holder.class).get(); + } + + @Override + public void afterEach(ExtensionContext context) { + ExtensionContext.Store store = context.getStore(ExtensionContext.Namespace.create(Expect.class)); + Holder holder = store.get(context.getUniqueId(), Holder.class); + List failures = null; + if (holder != null) { + synchronized (holder) { + failures = holder.failures; + if (failures == null) { + throw new IllegalStateException(); + } + holder.failures = null; + } + } + if (failures != null && !failures.isEmpty()) { + throw failures.size() == 1 ? failures.get(0) : new MultipleFailuresError(null, failures); + } + } + + private static class Holder implements FailureStrategy, ExtensionContext.Store.CloseableResource { + private List failures = new ArrayList<>(); + + private StandardSubjectBuilder get() { + return StandardSubjectBuilder.forCustomFailureStrategy(this); + } + + @Override + public synchronized void fail(AssertionError failure) { + failures.add(failure); + } + + @Override + public synchronized void close() { + if (failures != null) { + throw new IllegalStateException(); + } + } + } +} diff --git a/extensions/junit5/src/main/resources/META-INF/services/org.junit.jupiter.api.extension.Extension b/extensions/junit5/src/main/resources/META-INF/services/org.junit.jupiter.api.extension.Extension new file mode 100644 index 000000000..a8027f572 --- /dev/null +++ b/extensions/junit5/src/main/resources/META-INF/services/org.junit.jupiter.api.extension.Extension @@ -0,0 +1 @@ +com.google.common.truth.junit5.Expect \ No newline at end of file diff --git a/extensions/junit5/src/test/java/com/google/common/truth/junit5/ExpectTest.java b/extensions/junit5/src/test/java/com/google/common/truth/junit5/ExpectTest.java new file mode 100644 index 000000000..e3d106637 --- /dev/null +++ b/extensions/junit5/src/test/java/com/google/common/truth/junit5/ExpectTest.java @@ -0,0 +1,59 @@ +package com.google.common.truth.junit5; + +import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass; +import static org.junit.platform.testkit.engine.EventConditions.event; +import static org.junit.platform.testkit.engine.EventConditions.finishedWithFailure; +import static org.junit.platform.testkit.engine.EventConditions.test; + +import com.google.common.truth.StandardSubjectBuilder; +import org.assertj.core.api.Condition; +import org.junit.jupiter.api.MethodOrderer.OrderAnnotation; +import org.junit.jupiter.api.Order; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestMethodOrder; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.platform.testkit.engine.EngineTestKit; +import org.junit.platform.testkit.engine.Events; +import org.opentest4j.MultipleFailuresError; + +public class ExpectTest { + @Test + public void testExtension() { + Events events = EngineTestKit.engine("junit-jupiter") + .selectors(selectClass(TestCase.class)) + .execute() + .testEvents() + .assertStatistics(stats -> stats.started(3).failed(2)); + events.succeeded().assertEventsMatchExactly(event(test("testSuccess"))); + events.failed().assertEventsMatchExactly( + event(test("testOneFailure"), finishedWithFailure(new Condition(t -> t instanceof AssertionError && !(t instanceof MultipleFailuresError), "AssertionError"))), + event(test("testTwoFailures"), finishedWithFailure(new Condition(MultipleFailuresError.class::isInstance, "MultipleFailuresError")))); + } + + @ExtendWith(Expect.class) + @TestMethodOrder(OrderAnnotation.class) + static class TestCase { + @Test + @Order(1) + public void testSuccess(StandardSubjectBuilder expect) { + expect.that(0).isEqualTo(0); + expect.that(1).isEqualTo(1); + } + + @Test + @Order(2) + @ExtendWith(Expect.class) + public void testOneFailure(StandardSubjectBuilder expect) { + expect.that(0).isEqualTo(1); + expect.that(1).isEqualTo(1); + } + + @Test + @Order(3) + @ExtendWith(Expect.class) + public void testTwoFailures(StandardSubjectBuilder expect) { + expect.that(0).isEqualTo(1); + expect.that(1).isEqualTo(0); + } + } +} diff --git a/extensions/pom.xml b/extensions/pom.xml index a3af92922..9addb6c01 100644 --- a/extensions/pom.xml +++ b/extensions/pom.xml @@ -22,5 +22,6 @@ re2j liteproto proto + junit5 diff --git a/pom.xml b/pom.xml index f05917483..2c39ca17f 100644 --- a/pom.xml +++ b/pom.xml @@ -79,6 +79,26 @@ junit 4.12 + + org.junit.jupiter + junit-jupiter-api + 5.6.2 + + + org.junit.jupiter + junit-jupiter-engine + 5.6.2 + + + org.junit.platform + junit-platform-testkit + 1.6.2 + + + org.opentest4j + opentest4j + 1.2.0 + com.google.gwt gwt-user @@ -322,7 +342,7 @@ maven-surefire-plugin - 2.12.4 + 2.22.2 maven-enforcer-plugin