From 845e724bcaf58a2b6a342a0a601f6545c86d2e83 Mon Sep 17 00:00:00 2001 From: l-1sqared <30831153+l-1squared@users.noreply.github.com> Date: Mon, 24 Jan 2022 08:13:17 +0100 Subject: [PATCH 1/3] Issue-373: Wrap up Scenario earlier in JUnit5 Lifecycle Also update JGiven Framework tests to also test Junit5 Signed-off-by: l-1sqared <30831153+l-1squared@users.noreply.github.com> --- .../jgiven/junit5/JGivenExtension.java | 4 +- jgiven-tests/build.gradle | 4 ++ .../tngtech/jgiven/tags/FeatureJUnit5.java | 13 ++++ .../jgiven/tests/GuaranteedFieldRealTest.java | 3 +- .../JGivenReportExtractingExtension.java | 35 +++++++++ .../jgiven/tests/JUnit5AfterMethodTests.java | 48 +++++++++++++ .../jgiven/tests/ScenarioTestForTesting.java | 2 + .../tests/TestClassWithDescription.java | 8 ++- .../tests/TestClassWithOnlyIgnoredTests.java | 9 ++- .../jgiven/tests/TestScenarioRepository.java | 4 ++ .../tngtech/jgiven/tests/TestScenarios.java | 18 ++++- .../TestWithExceptionsInAfterMethod.java | 12 ++-- .../com/tngtech/jgiven/GivenScenarioTest.java | 29 ++++---- .../{junit => impl}/RepeatedStageUseTest.java | 3 +- .../SimpleStageRepetitionUseTest.java | 3 +- .../tngtech/jgiven/junit/JUnitExecutor.java | 2 +- .../jgiven/junit/JUnitExecutorTest.java | 45 ++++++------ .../tngtech/jgiven/junit5/JUnit5Executor.java | 39 ++++++++++ .../jgiven/junit5/JUnit5ExecutorTest.java | 72 +++++++++++++++++++ .../junit5/Junit5TestExecutionResult.java | 47 ++++++++++++ .../junit5/TestExecutionResultProvider.java | 47 ++++++++++++ .../jgiven/testframework/TestExecutor.java | 13 ++-- .../jgiven/testframework/TestFramework.java | 3 +- .../TestFrameworkExecutionTest.java | 70 +++++++++--------- .../testframework/ThenTestFramework.java | 31 ++++---- .../testframework/WhenTestFramework.java | 30 +++++--- .../tngtech/jgiven/testng/TestNgExecutor.java | 2 +- 27 files changed, 476 insertions(+), 120 deletions(-) create mode 100644 jgiven-tests/src/main/java/com/tngtech/jgiven/tags/FeatureJUnit5.java create mode 100644 jgiven-tests/src/main/java/com/tngtech/jgiven/tests/JGivenReportExtractingExtension.java create mode 100644 jgiven-tests/src/main/java/com/tngtech/jgiven/tests/JUnit5AfterMethodTests.java rename jgiven-tests/src/test/java/com/tngtech/jgiven/{junit => impl}/RepeatedStageUseTest.java (97%) rename jgiven-tests/src/test/java/com/tngtech/jgiven/{junit => impl}/SimpleStageRepetitionUseTest.java (97%) create mode 100644 jgiven-tests/src/test/java/com/tngtech/jgiven/junit5/JUnit5Executor.java create mode 100644 jgiven-tests/src/test/java/com/tngtech/jgiven/junit5/JUnit5ExecutorTest.java create mode 100644 jgiven-tests/src/test/java/com/tngtech/jgiven/junit5/Junit5TestExecutionResult.java create mode 100644 jgiven-tests/src/test/java/com/tngtech/jgiven/junit5/TestExecutionResultProvider.java diff --git a/jgiven-junit5/src/main/java/com/tngtech/jgiven/junit5/JGivenExtension.java b/jgiven-junit5/src/main/java/com/tngtech/jgiven/junit5/JGivenExtension.java index 65ff5d034e..cd54b1eb34 100644 --- a/jgiven-junit5/src/main/java/com/tngtech/jgiven/junit5/JGivenExtension.java +++ b/jgiven-junit5/src/main/java/com/tngtech/jgiven/junit5/JGivenExtension.java @@ -39,7 +39,7 @@ public class JGivenExtension implements BeforeAllCallback, AfterAllCallback, BeforeEachCallback, - AfterEachCallback { + AfterTestExecutionCallback { private static final Namespace NAMESPACE = Namespace.create("com.tngtech.jgiven"); @@ -75,7 +75,7 @@ public void beforeEach(ExtensionContext context) { } @Override - public void afterEach(ExtensionContext context) throws Exception { + public void afterTestExecution(ExtensionContext context) throws Exception { ScenarioBase scenario = getScenario(); try { if (context.getExecutionException().isPresent()) { diff --git a/jgiven-tests/build.gradle b/jgiven-tests/build.gradle index c0cff05ee8..b65bebc85a 100644 --- a/jgiven-tests/build.gradle +++ b/jgiven-tests/build.gradle @@ -3,8 +3,12 @@ description = 'JGiven Tests - Contains BDD tests for JGiven written in JGiven' dependencies { implementation project(':jgiven-junit') implementation project(':jgiven-testng') + implementation project(':jgiven-junit5') implementation project(':jgiven-html5-report') implementation "junit:junit:$junitVersion" + implementation "org.junit.jupiter:junit-jupiter-api:5.8.2" + implementation "org.junit.jupiter:junit-jupiter-engine:5.8.2" + implementation "org.junit.platform:junit-platform-runner:1.8.2" implementation "org.testng:testng:$testngVersion" implementation "com.tngtech.java:junit-dataprovider:$junitDataproviderVersion" implementation 'com.beust:jcommander:1.82' diff --git a/jgiven-tests/src/main/java/com/tngtech/jgiven/tags/FeatureJUnit5.java b/jgiven-tests/src/main/java/com/tngtech/jgiven/tags/FeatureJUnit5.java new file mode 100644 index 0000000000..02fdadec5b --- /dev/null +++ b/jgiven-tests/src/main/java/com/tngtech/jgiven/tags/FeatureJUnit5.java @@ -0,0 +1,13 @@ +package com.tngtech.jgiven.tags; + +import com.tngtech.jgiven.annotation.IsTag; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +@FeatureTestFramework +@IsTag( name = "JUnit5", + description = "Tests can be be executed with JUnit5" ) +@Retention( RetentionPolicy.RUNTIME ) +public @interface FeatureJUnit5 { + +} diff --git a/jgiven-tests/src/main/java/com/tngtech/jgiven/tests/GuaranteedFieldRealTest.java b/jgiven-tests/src/main/java/com/tngtech/jgiven/tests/GuaranteedFieldRealTest.java index 4cf6fabd80..21b3d6e274 100644 --- a/jgiven-tests/src/main/java/com/tngtech/jgiven/tests/GuaranteedFieldRealTest.java +++ b/jgiven-tests/src/main/java/com/tngtech/jgiven/tests/GuaranteedFieldRealTest.java @@ -6,7 +6,8 @@ import com.tngtech.jgiven.junit.ScenarioTest; import org.junit.Test; -public class GuaranteedFieldRealTest extends ScenarioTest { +public class GuaranteedFieldRealTest extends + ScenarioTest { @Test public void a_sample_test() { diff --git a/jgiven-tests/src/main/java/com/tngtech/jgiven/tests/JGivenReportExtractingExtension.java b/jgiven-tests/src/main/java/com/tngtech/jgiven/tests/JGivenReportExtractingExtension.java new file mode 100644 index 0000000000..58d5b4fa7e --- /dev/null +++ b/jgiven-tests/src/main/java/com/tngtech/jgiven/tests/JGivenReportExtractingExtension.java @@ -0,0 +1,35 @@ +package com.tngtech.jgiven.tests; + +import com.tngtech.jgiven.exception.JGivenWrongUsageException; +import com.tngtech.jgiven.impl.ScenarioBase; +import com.tngtech.jgiven.impl.ScenarioHolder; +import com.tngtech.jgiven.junit5.JGivenExtension; +import com.tngtech.jgiven.report.model.ReportModel; +import java.util.Map; +import java.util.Optional; +import java.util.concurrent.ConcurrentHashMap; +import org.junit.jupiter.api.extension.ExtensionContext; + +/** + * Extracts the report Model from within the Test context before it gets deleted. + */ +public class JGivenReportExtractingExtension extends JGivenExtension { + + private static final Map, ReportModel> modelHolder = new ConcurrentHashMap<>(); + + public static Optional getReportModelFor(Class testClass) { + return Optional.ofNullable(modelHolder.get(testClass)); + } + + + @Override + public void afterTestExecution(ExtensionContext context) throws Exception { + Class testClass = context.getTestClass() + .orElseThrow(() -> new JGivenWrongUsageException("tests without test class are not supported yet")); + Optional.ofNullable(ScenarioHolder.get()) + .map(ScenarioHolder::getScenarioOfCurrentThread) + .map(ScenarioBase::getModel) + .ifPresent(model -> modelHolder.put(testClass, model)); + super.afterTestExecution(context); + } +} diff --git a/jgiven-tests/src/main/java/com/tngtech/jgiven/tests/JUnit5AfterMethodTests.java b/jgiven-tests/src/main/java/com/tngtech/jgiven/tests/JUnit5AfterMethodTests.java new file mode 100644 index 0000000000..6de7bbd3eb --- /dev/null +++ b/jgiven-tests/src/main/java/com/tngtech/jgiven/tests/JUnit5AfterMethodTests.java @@ -0,0 +1,48 @@ +package com.tngtech.jgiven.tests; + +import com.tngtech.jgiven.base.ScenarioTestBase; +import com.tngtech.jgiven.impl.Scenario; +import com.tngtech.jgiven.report.model.ScenarioCaseModel; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; + +@ExtendWith(JGivenReportExtractingExtension.class) +public class JUnit5AfterMethodTests extends ScenarioTestBase { + + private final Scenario scenario = createScenario(); + + @Override + public Scenario getScenario() { + return scenario; + } + + @Test + public void a_failing_JUnit_5_test() { + given().nothing(); + when().a_step_fails(); + then().something_happened(); + } + + @Test + public void a_succeeding_JUnit5_test() { + given().nothing(); + when().something_happens(); + then().something_happened(); + } + + @Test + @Disabled + public void a_skipped_JUnit5_test() { + given().nothing(); + when().something_happens(); + then().something_happened(); + } + + + @AfterEach + public void modifyScenario() { + getScenario().getModel().getLastScenarioModel().addCase(new ScenarioCaseModel()); + } +} diff --git a/jgiven-tests/src/main/java/com/tngtech/jgiven/tests/ScenarioTestForTesting.java b/jgiven-tests/src/main/java/com/tngtech/jgiven/tests/ScenarioTestForTesting.java index 484a47a3b0..8abf462db3 100644 --- a/jgiven-tests/src/main/java/com/tngtech/jgiven/tests/ScenarioTestForTesting.java +++ b/jgiven-tests/src/main/java/com/tngtech/jgiven/tests/ScenarioTestForTesting.java @@ -3,7 +3,9 @@ import com.tngtech.jgiven.impl.Scenario; import com.tngtech.jgiven.junit.ScenarioTest; import com.tngtech.jgiven.impl.ScenarioHolder; +import org.junit.jupiter.api.extension.ExtendWith; +@ExtendWith(JGivenReportExtractingExtension.class) public class ScenarioTestForTesting extends ScenarioTest { @Override public Scenario getScenario() { diff --git a/jgiven-tests/src/main/java/com/tngtech/jgiven/tests/TestClassWithDescription.java b/jgiven-tests/src/main/java/com/tngtech/jgiven/tests/TestClassWithDescription.java index 14af9f253d..43025d5151 100644 --- a/jgiven-tests/src/main/java/com/tngtech/jgiven/tests/TestClassWithDescription.java +++ b/jgiven-tests/src/main/java/com/tngtech/jgiven/tests/TestClassWithDescription.java @@ -1,17 +1,19 @@ package com.tngtech.jgiven.tests; -import org.junit.Test; -import org.testng.annotations.Listeners; - import com.tngtech.jgiven.annotation.Description; import com.tngtech.jgiven.junit.ScenarioTest; import com.tngtech.jgiven.testng.ScenarioTestListener; +import org.junit.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.testng.annotations.Listeners; @Listeners( ScenarioTestListener.class ) +@ExtendWith(JGivenReportExtractingExtension.class) @Description( "Test Description" ) public class TestClassWithDescription extends ScenarioTest { @Test + @org.junit.jupiter.api.Test @org.testng.annotations.Test public void some_test() { given().nothing(); diff --git a/jgiven-tests/src/main/java/com/tngtech/jgiven/tests/TestClassWithOnlyIgnoredTests.java b/jgiven-tests/src/main/java/com/tngtech/jgiven/tests/TestClassWithOnlyIgnoredTests.java index 209b38ac8e..73b7e46dff 100644 --- a/jgiven-tests/src/main/java/com/tngtech/jgiven/tests/TestClassWithOnlyIgnoredTests.java +++ b/jgiven-tests/src/main/java/com/tngtech/jgiven/tests/TestClassWithOnlyIgnoredTests.java @@ -1,17 +1,20 @@ package com.tngtech.jgiven.tests; +import com.tngtech.jgiven.testng.ScenarioTestListener; import org.junit.Ignore; import org.junit.Test; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.extension.ExtendWith; import org.testng.annotations.Listeners; -import com.tngtech.jgiven.junit.ScenarioTest; -import com.tngtech.jgiven.testng.ScenarioTestListener; - @Listeners( ScenarioTestListener.class ) +@ExtendWith(JGivenReportExtractingExtension.class) public class TestClassWithOnlyIgnoredTests extends ScenarioTestForTesting { @Ignore @Test + @org.junit.jupiter.api.Test + @Disabled @org.testng.annotations.Test( enabled = false ) public void test() { diff --git a/jgiven-tests/src/main/java/com/tngtech/jgiven/tests/TestScenarioRepository.java b/jgiven-tests/src/main/java/com/tngtech/jgiven/tests/TestScenarioRepository.java index 7961a95190..40eda3ee6a 100644 --- a/jgiven-tests/src/main/java/com/tngtech/jgiven/tests/TestScenarioRepository.java +++ b/jgiven-tests/src/main/java/com/tngtech/jgiven/tests/TestScenarioRepository.java @@ -274,6 +274,10 @@ public static TestScenario testWithTwoCasesAndTheFirstOneFails() { return new TestScenario( TestWithTwoCasesAndAFailingOne.class ); } + public static TestScenario junit5TestsWithModificationsInAfterMethod(){ + return new TestScenario(JUnit5AfterMethodTests.class); + } + public static TestScenario testNgTestWithAFailingCase() { return new TestScenario( FailingCasesTestNgTest.class ); } diff --git a/jgiven-tests/src/main/java/com/tngtech/jgiven/tests/TestScenarios.java b/jgiven-tests/src/main/java/com/tngtech/jgiven/tests/TestScenarios.java index b4cc2733d4..c200cc6673 100644 --- a/jgiven-tests/src/main/java/com/tngtech/jgiven/tests/TestScenarios.java +++ b/jgiven-tests/src/main/java/com/tngtech/jgiven/tests/TestScenarios.java @@ -1,15 +1,17 @@ package com.tngtech.jgiven.tests; -import org.junit.Test; -import org.testng.annotations.Listeners; - import com.tngtech.jgiven.annotation.Pending; import com.tngtech.jgiven.testng.ScenarioTestListener; +import org.junit.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.testng.annotations.Listeners; @Listeners( ScenarioTestListener.class ) +@ExtendWith(JGivenReportExtractingExtension.class) public class TestScenarios extends ScenarioTestForTesting { @Test + @org.junit.jupiter.api.Test @org.testng.annotations.Test public void failing_test_with_two_steps() { given().an_exception_is_thrown(); @@ -17,6 +19,7 @@ public void failing_test_with_two_steps() { } @Test + @org.junit.jupiter.api.Test @org.testng.annotations.Test public void failing_test_with_three_steps() { given().an_exception_is_thrown(); @@ -25,6 +28,7 @@ public void failing_test_with_three_steps() { } @Test + @org.junit.jupiter.api.Test @org.testng.annotations.Test public void failing_test_with_two_steps_and_second_step_fails() { given().nothing(); @@ -32,6 +36,7 @@ public void failing_test_with_two_steps_and_second_step_fails() { } @Test + @org.junit.jupiter.api.Test @org.testng.annotations.Test public void failing_test_with_two_failing_stages() { given().an_exception_is_thrown(); @@ -39,6 +44,7 @@ public void failing_test_with_two_failing_stages() { } @Test + @org.junit.jupiter.api.Test @org.testng.annotations.Test public void failing_test_where_second_stage_has_a_failing_after_stage_method() { FailingAfterStageMethodStage stage = addStage( FailingAfterStageMethodStage.class ); @@ -47,6 +53,7 @@ public void failing_test_where_second_stage_has_a_failing_after_stage_method() { } @Test + @org.junit.jupiter.api.Test @org.testng.annotations.Test @Pending public void failing_test_with_Pending_annotation() { @@ -55,6 +62,7 @@ public void failing_test_with_Pending_annotation() { } @Test + @org.junit.jupiter.api.Test @org.testng.annotations.Test @Pending public void passing_test_with_Pending_annotation() { @@ -62,6 +70,7 @@ public void passing_test_with_Pending_annotation() { } @Test + @org.junit.jupiter.api.Test @org.testng.annotations.Test @Pending( failIfPass = true ) public void passing_test_with_Pending_annotation_and_failIfPassed_set_to_true() { @@ -69,6 +78,7 @@ public void passing_test_with_Pending_annotation_and_failIfPassed_set_to_true() } @Test + @org.junit.jupiter.api.Test @org.testng.annotations.Test @Pending( failIfPass = true ) public void failing_test_with_Pending_annotation_and_failIfPassed_set_to_true() { @@ -76,6 +86,7 @@ public void failing_test_with_Pending_annotation_and_failIfPassed_set_to_true() } @Test + @org.junit.jupiter.api.Test @org.testng.annotations.Test @Pending( failIfPass = true, executeSteps = true ) public void failing_test_with_Pending_annotation_and_executeSteps_set_to_true() { @@ -83,6 +94,7 @@ public void failing_test_with_Pending_annotation_and_executeSteps_set_to_true() } @Test + @org.junit.jupiter.api.Test @org.testng.annotations.Test @TestTag( "testValue" ) public void test_with_tag_annotation() { diff --git a/jgiven-tests/src/main/java/com/tngtech/jgiven/tests/TestWithExceptionsInAfterMethod.java b/jgiven-tests/src/main/java/com/tngtech/jgiven/tests/TestWithExceptionsInAfterMethod.java index 1371bd5ed8..a9dd7e557d 100644 --- a/jgiven-tests/src/main/java/com/tngtech/jgiven/tests/TestWithExceptionsInAfterMethod.java +++ b/jgiven-tests/src/main/java/com/tngtech/jgiven/tests/TestWithExceptionsInAfterMethod.java @@ -2,17 +2,21 @@ import org.junit.After; import org.junit.Test; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.extension.ExtendWith; -import com.tngtech.jgiven.junit.ScenarioTest; - -public class TestWithExceptionsInAfterMethod extends ScenarioTestForTesting { +@ExtendWith(JGivenReportExtractingExtension.class) +public class TestWithExceptionsInAfterMethod + extends ScenarioTestForTesting { @After + @AfterEach public void afterException() { - throw new IllegalStateException( "exception in after method" ); + throw new IllegalStateException("exception in after method"); } @Test + @org.junit.jupiter.api.Test public void test_that_exception_in_scenario_is_not_hidden_by_exception_in_JUnit_after_method() { given().nothing(); when().a_step_fails(); diff --git a/jgiven-tests/src/test/java/com/tngtech/jgiven/GivenScenarioTest.java b/jgiven-tests/src/test/java/com/tngtech/jgiven/GivenScenarioTest.java index 15be14a41e..e70cd92743 100644 --- a/jgiven-tests/src/test/java/com/tngtech/jgiven/GivenScenarioTest.java +++ b/jgiven-tests/src/test/java/com/tngtech/jgiven/GivenScenarioTest.java @@ -40,12 +40,12 @@ public SELF a_failing_test() { return self(); } - public SELF the_test_has_$_failing_stages( int n ) { + public SELF the_test_has_$_failing_stages(int n) { criteria.numberOfFailingStages = n; return self(); } - public SELF stage_$_has_a_failing_after_stage_method( int i ) { + public SELF stage_$_has_a_failing_after_stage_method(int i) { criteria.stageWithFailingAfterStageMethod = i; return self(); } @@ -65,35 +65,35 @@ public SELF executeSteps_set_to_true() { return self(); } - public SELF the_test_has_a_tag_annotation_named( String name ) { - assertThat( name ).isEqualTo( "TestTag" ); + public SELF the_test_has_a_tag_annotation_named(String name) { + assertThat(name).isEqualTo("TestTag"); criteria.tagAnnotation = true; return self(); } @AfterStage public void findScenario() { - if( testScenario == null ) { - testScenario = TestScenarioRepository.findScenario( criteria ); + if (testScenario == null) { + testScenario = TestScenarioRepository.findScenario(criteria); } } - public SELF a_failing_test_with_$_steps( int n ) { + public SELF a_failing_test_with_$_steps(int n) { a_failing_test(); - return a_test_with_$_steps( n ); + return a_test_with_$_steps(n); } - public SELF a_test_with_$_steps( int n ) { + public SELF a_test_with_$_steps(int n) { criteria.numberOfSteps = n; return self(); } - public SELF step_$_fails( int i ) { + public SELF step_$_fails(int i) { criteria.failingStep = i; return self(); } - public SELF the_test_class_has_a_description_annotation_with_value( String value ) { + public SELF the_test_class_has_a_description_annotation_with_value(String value) { criteria.testClassDescription = value; return self(); } @@ -103,7 +103,7 @@ public SELF a_JUnit_test_class_with_the_Parameterized_Runner() { return self(); } - public SELF the_test_class_has_$_parameters( int nParameters ) { + public SELF the_test_class_has_$_parameters(int nParameters) { criteria.numberOfParameters = nParameters; return self(); } @@ -127,4 +127,9 @@ public void a_TestNG_test_with_two_cases_and_the_first_one_fails() { public void a_pending_scenario_with_a_data_provider() { testScenario = new TestScenario(PendingDataProviderTests.class); } + + public SELF junit5_tests_with_scenario_modifications_in_after_method() { + testScenario = TestScenarioRepository.junit5TestsWithModificationsInAfterMethod(); + return self(); + } } diff --git a/jgiven-tests/src/test/java/com/tngtech/jgiven/junit/RepeatedStageUseTest.java b/jgiven-tests/src/test/java/com/tngtech/jgiven/impl/RepeatedStageUseTest.java similarity index 97% rename from jgiven-tests/src/test/java/com/tngtech/jgiven/junit/RepeatedStageUseTest.java rename to jgiven-tests/src/test/java/com/tngtech/jgiven/impl/RepeatedStageUseTest.java index 5ec034b1f1..7cc519765b 100644 --- a/jgiven-tests/src/test/java/com/tngtech/jgiven/junit/RepeatedStageUseTest.java +++ b/jgiven-tests/src/test/java/com/tngtech/jgiven/impl/RepeatedStageUseTest.java @@ -1,4 +1,4 @@ -package com.tngtech.jgiven.junit; +package com.tngtech.jgiven.impl; import static org.assertj.core.api.Assertions.assertThat; @@ -9,6 +9,7 @@ import com.tngtech.jgiven.annotation.ProvidedScenarioState; import com.tngtech.jgiven.annotation.ScenarioStage; import com.tngtech.jgiven.annotation.ScenarioState; +import com.tngtech.jgiven.junit.ScenarioTest; import org.junit.Test; public class RepeatedStageUseTest extends ScenarioTest< diff --git a/jgiven-tests/src/test/java/com/tngtech/jgiven/junit/SimpleStageRepetitionUseTest.java b/jgiven-tests/src/test/java/com/tngtech/jgiven/impl/SimpleStageRepetitionUseTest.java similarity index 97% rename from jgiven-tests/src/test/java/com/tngtech/jgiven/junit/SimpleStageRepetitionUseTest.java rename to jgiven-tests/src/test/java/com/tngtech/jgiven/impl/SimpleStageRepetitionUseTest.java index 638ffc8c76..6c6cee4daa 100644 --- a/jgiven-tests/src/test/java/com/tngtech/jgiven/junit/SimpleStageRepetitionUseTest.java +++ b/jgiven-tests/src/test/java/com/tngtech/jgiven/impl/SimpleStageRepetitionUseTest.java @@ -1,4 +1,4 @@ -package com.tngtech.jgiven.junit; +package com.tngtech.jgiven.impl; import static org.assertj.core.api.Assertions.assertThat; @@ -6,6 +6,7 @@ import com.tngtech.jgiven.annotation.AfterStage; import com.tngtech.jgiven.annotation.BeforeStage; import com.tngtech.jgiven.annotation.ScenarioStage; +import com.tngtech.jgiven.junit.SimpleScenarioTest; import org.junit.Ignore; import org.junit.Test; diff --git a/jgiven-tests/src/test/java/com/tngtech/jgiven/junit/JUnitExecutor.java b/jgiven-tests/src/test/java/com/tngtech/jgiven/junit/JUnitExecutor.java index ac619ecc44..3213d36e74 100644 --- a/jgiven-tests/src/test/java/com/tngtech/jgiven/junit/JUnitExecutor.java +++ b/jgiven-tests/src/test/java/com/tngtech/jgiven/junit/JUnitExecutor.java @@ -41,7 +41,7 @@ public Request supply() { } @Override - public TestExecutionResult execute( final Class testClass ) { + public TestExecutionResult execute(final Class testClass ) { return execute( new RequestSupplier() { @Override public Request supply() { diff --git a/jgiven-tests/src/test/java/com/tngtech/jgiven/junit/JUnitExecutorTest.java b/jgiven-tests/src/test/java/com/tngtech/jgiven/junit/JUnitExecutorTest.java index 637c205695..d3dc4283e4 100644 --- a/jgiven-tests/src/test/java/com/tngtech/jgiven/junit/JUnitExecutorTest.java +++ b/jgiven-tests/src/test/java/com/tngtech/jgiven/junit/JUnitExecutorTest.java @@ -10,19 +10,20 @@ import com.tngtech.jgiven.testframework.WhenTestFramework; @FeatureJUnit -public class JUnitExecutorTest extends JGivenScenarioTest, WhenTestFramework, ThenTestFramework> { +public class JUnitExecutorTest + extends JGivenScenarioTest, WhenTestFramework, ThenTestFramework> { @Test public void the_JUnit_Parametrized_runner_creates_correct_cases() { given().a_JUnit_test_class_with_the_Parameterized_Runner() - .and().the_test_class_has_$_parameters( 2 ); + .and().the_test_class_has_$_parameters(2); when().the_test_class_is_executed_with_JUnit(); then().the_report_model_contains_one_scenario_for_each_test_method() - .and().each_scenario_contains_$_cases( 2 ); + .and().each_scenario_contains_$_cases(2); } @Test - @Issue( "#25" ) + @Issue("#25") public void test_classes_with_only_ignored_test_result_in_a_valid_report() { given().a_test_class_with_all_tests_ignored(); when().the_test_class_is_executed_with_JUnit(); @@ -31,44 +32,44 @@ public void test_classes_with_only_ignored_test_result_in_a_valid_report() { } @Test - @Issue( "#49" ) + @Issue("#49") public void exception_in_scenario_is_not_hidden_by_exception_in_JUnit_after_method() { given().a_test_class_with_a_failing_scenario_and_a_failing_after_stage(); when().the_test_class_is_executed_with_JUnit(); - then().the_test_fails_with_message( "assertion failed in test step" ); + then().the_test_fails_with_message("assertion failed in test step"); } @Test public void steps_following_failing_steps_are_reported_as_skipped() { - given().a_failing_test_with_$_steps( 3 ) - .and().step_$_fails( 1 ); + given().a_failing_test_with_$_steps(3) + .and().step_$_fails(1); when().the_test_is_executed_with_JUnit(); - then().step_$_is_reported_as_failed( 1 ) - .and().step_$_is_reported_as_skipped( 2 ) - .and().step_$_is_reported_as_skipped( 3 ); + then().step_$_is_reported_as_failed(1) + .and().step_$_is_reported_as_skipped(2) + .and().step_$_is_reported_as_skipped(3); } @Test public void after_stage_methods_of_stages_following_failing_stages_are_ignored() { - given().a_failing_test_with_$_steps( 2 ) - .and().the_test_has_$_failing_stages( 2 ) - .and().stage_$_has_a_failing_after_stage_method( 2 ) - .and().step_$_fails( 1 ); + given().a_failing_test_with_$_steps(2) + .and().the_test_has_$_failing_stages(2) + .and().stage_$_has_a_failing_after_stage_method(2) + .and().step_$_fails(1); when().the_test_is_executed_with_JUnit(); then().the_test_fails() - .and().step_$_is_reported_as_failed( 1 ) - .and().step_$_is_reported_as_skipped( 2 ); + .and().step_$_is_reported_as_failed(1) + .and().step_$_is_reported_as_skipped(2); } @Test public void all_steps_of_stages_following_failing_stages_are_ignored() { - given().a_failing_test_with_$_steps( 2 ) - .and().the_test_has_$_failing_stages( 2 ) - .and().step_$_fails( 1 ); + given().a_failing_test_with_$_steps(2) + .and().the_test_has_$_failing_stages(2) + .and().step_$_fails(1); when().the_test_is_executed_with_JUnit(); then().the_test_fails() - .and().step_$_is_reported_as_failed( 1 ) - .and().step_$_is_reported_as_skipped( 2 ); + .and().step_$_is_reported_as_failed(1) + .and().step_$_is_reported_as_skipped(2); } diff --git a/jgiven-tests/src/test/java/com/tngtech/jgiven/junit5/JUnit5Executor.java b/jgiven-tests/src/test/java/com/tngtech/jgiven/junit5/JUnit5Executor.java new file mode 100644 index 0000000000..d1959d7816 --- /dev/null +++ b/jgiven-tests/src/test/java/com/tngtech/jgiven/junit5/JUnit5Executor.java @@ -0,0 +1,39 @@ +package com.tngtech.jgiven.junit5; + +import com.tngtech.jgiven.testframework.TestExecutionResult; +import com.tngtech.jgiven.testframework.TestExecutor; +import org.junit.platform.engine.discovery.DiscoverySelectors; +import org.junit.platform.launcher.Launcher; +import org.junit.platform.launcher.LauncherDiscoveryRequest; +import org.junit.platform.launcher.LauncherSession; +import org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder; +import org.junit.platform.launcher.core.LauncherFactory; + +public class JUnit5Executor extends TestExecutor { + + @Override + public TestExecutionResult execute(Class testClass, String testMethod) { + LauncherDiscoveryRequest launcherRequest = LauncherDiscoveryRequestBuilder.request() + .selectors(DiscoverySelectors.selectMethod(testClass, testMethod)) + .build(); + return run(launcherRequest); + } + + @Override + public TestExecutionResult execute(Class testClass) { + LauncherDiscoveryRequest launcherRequest = + LauncherDiscoveryRequestBuilder.request().selectors(DiscoverySelectors.selectClass(testClass)).build(); + return run(launcherRequest); + } + + private TestExecutionResult run(LauncherDiscoveryRequest launchRequest) { + TestExecutionResultProvider listener = new TestExecutionResultProvider(); + + try (LauncherSession session = LauncherFactory.openSession()) { + Launcher launcher = session.getLauncher(); + launcher.registerTestExecutionListeners(listener); + launcher.execute(launchRequest); + return listener.getExecutionResult(); + } + } +} diff --git a/jgiven-tests/src/test/java/com/tngtech/jgiven/junit5/JUnit5ExecutorTest.java b/jgiven-tests/src/test/java/com/tngtech/jgiven/junit5/JUnit5ExecutorTest.java new file mode 100644 index 0000000000..0e08ce5137 --- /dev/null +++ b/jgiven-tests/src/test/java/com/tngtech/jgiven/junit5/JUnit5ExecutorTest.java @@ -0,0 +1,72 @@ +package com.tngtech.jgiven.junit5; + +import com.tngtech.jgiven.GivenScenarioTest; +import com.tngtech.jgiven.JGivenScenarioTest; +import com.tngtech.jgiven.tags.FeatureJUnit5; +import com.tngtech.jgiven.tags.Issue; +import com.tngtech.jgiven.testframework.ThenTestFramework; +import com.tngtech.jgiven.testframework.WhenTestFramework; +import org.junit.Test; + +@FeatureJUnit5 +public class JUnit5ExecutorTest + extends JGivenScenarioTest, WhenTestFramework, ThenTestFramework> { + + + @Test + public void tests_with_scenario_modifications_in_after_method() { + given().junit5_tests_with_scenario_modifications_in_after_method(); + when().the_test_class_is_executed_with_JUnit5(); + then().each_scenario_contains_$_cases(2); + } + + @Test + @Issue("#25") + public void a_valid_report_is_generated_for_classes_that_are_disabled() { + given().a_test_class_with_all_tests_ignored(); + when().the_test_class_is_executed_with_JUnit5(); + then().the_report_model_is_either_null_or_empty() + .and().has_a_valid_class_name_if_it_is_not_null(); + } + + @Test + @Issue("#49") + public void exception_in_scenario_is_not_hidden_by_exception_in_JUnit_after_method() { + given().a_test_class_with_a_failing_scenario_and_a_failing_after_stage(); + when().the_test_class_is_executed_with_JUnit5(); + then().the_test_fails_with_message("assertion failed in test step"); + } + + @Test + public void steps_following_failing_steps_are_reported_as_skipped() { + given().a_failing_test_with_$_steps(3) + .and().step_$_fails(1); + when().the_test_is_executed_with_JUnit5(); + then().step_$_is_reported_as_failed(1) + .and().step_$_is_reported_as_skipped(2) + .and().step_$_is_reported_as_skipped(3); + } + + @Test + public void after_stage_methods_of_stages_following_failing_stages_are_ignored() { + given().a_failing_test_with_$_steps(2) + .and().the_test_has_$_failing_stages(2) + .and().stage_$_has_a_failing_after_stage_method(2) + .and().step_$_fails(1); + when().the_test_is_executed_with_JUnit5(); + then().the_test_fails() + .and().step_$_is_reported_as_failed(1) + .and().step_$_is_reported_as_skipped(2); + } + + @Test + public void all_steps_of_stages_following_failing_stages_are_ignored() { + given().a_failing_test_with_$_steps(2) + .and().the_test_has_$_failing_stages(2) + .and().step_$_fails(1); + when().the_test_is_executed_with_JUnit(); + then().the_test_fails() + .and().step_$_is_reported_as_failed(1) + .and().step_$_is_reported_as_skipped(2); + } +} diff --git a/jgiven-tests/src/test/java/com/tngtech/jgiven/junit5/Junit5TestExecutionResult.java b/jgiven-tests/src/test/java/com/tngtech/jgiven/junit5/Junit5TestExecutionResult.java new file mode 100644 index 0000000000..aec1634c0b --- /dev/null +++ b/jgiven-tests/src/test/java/com/tngtech/jgiven/junit5/Junit5TestExecutionResult.java @@ -0,0 +1,47 @@ +package com.tngtech.jgiven.junit5; + +import com.tngtech.jgiven.report.model.ReportModel; +import com.tngtech.jgiven.testframework.TestExecutionResult; +import java.lang.reflect.Method; +import java.util.Map; + +class Junit5TestExecutionResult extends TestExecutionResult { + + private final ReportModel report; + private final Map result; + + public Junit5TestExecutionResult(ReportModel report, + Map result) { + this.report = report; + this.result = result; + } + + + @Override + public int getFailureCount() { + return (int) result.values().stream() + .filter(result -> result.getStatus() == org.junit.platform.engine.TestExecutionResult.Status.FAILED) + .count(); + } + + @Override + public String getFailureMessage(int i) { + if (result.size() == 1 && i == 0) { + return result.values().stream() + .findFirst() + .flatMap(org.junit.platform.engine.TestExecutionResult::getThrowable) + .map(Throwable::getMessage) + .orElseThrow(() -> new IllegalStateException("Expected failure message for test 1, but none present.")); + + } else { + throw new UnsupportedOperationException("Cannot address a map by Index." + + "This method is a result of attempting to adapt to data model that was focused on JUnit4. " + + "Please refactor."); + } + } + + @Override + public ReportModel getReportModel() { + return report; + } +} diff --git a/jgiven-tests/src/test/java/com/tngtech/jgiven/junit5/TestExecutionResultProvider.java b/jgiven-tests/src/test/java/com/tngtech/jgiven/junit5/TestExecutionResultProvider.java new file mode 100644 index 0000000000..2417b8775a --- /dev/null +++ b/jgiven-tests/src/test/java/com/tngtech/jgiven/junit5/TestExecutionResultProvider.java @@ -0,0 +1,47 @@ +package com.tngtech.jgiven.junit5; + +import com.tngtech.jgiven.report.model.ReportModel; +import com.tngtech.jgiven.testframework.TestExecutionResult; +import com.tngtech.jgiven.tests.JGivenReportExtractingExtension; +import java.lang.reflect.Method; +import java.util.HashMap; +import java.util.Map; +import org.junit.platform.engine.TestSource; +import org.junit.platform.engine.support.descriptor.ClassSource; +import org.junit.platform.engine.support.descriptor.MethodSource; +import org.junit.platform.launcher.TestExecutionListener; +import org.junit.platform.launcher.TestIdentifier; + +class TestExecutionResultProvider implements TestExecutionListener { + + private Map executionResults = new HashMap<>(); + + + private ReportModel reportModel; + + @Override + public void executionFinished(TestIdentifier testIdentifier, + org.junit.platform.engine.TestExecutionResult testExecutionResult) { + TestSource testSource = testIdentifier.getSource().orElse(null); + if (testSource instanceof MethodSource) { + handleTestMethodFinished((MethodSource) testSource, testExecutionResult); + } else if (testSource instanceof ClassSource) { + handleTestClassFinished((ClassSource) testSource); + } + } + + private void handleTestClassFinished(ClassSource testSource) { + this.reportModel = JGivenReportExtractingExtension.getReportModelFor(testSource.getJavaClass()) + .orElseThrow(() -> new IllegalStateException("Failed to obtain report model for tested class.")); + } + + private void handleTestMethodFinished(MethodSource testSource, + org.junit.platform.engine.TestExecutionResult testExecutionResult) { + executionResults.put(testSource.getJavaMethod(), testExecutionResult); + } + + + public TestExecutionResult getExecutionResult() { + return new Junit5TestExecutionResult(reportModel, executionResults); + } +} diff --git a/jgiven-tests/src/test/java/com/tngtech/jgiven/testframework/TestExecutor.java b/jgiven-tests/src/test/java/com/tngtech/jgiven/testframework/TestExecutor.java index 3e0e2174e3..cd1bb18e29 100644 --- a/jgiven-tests/src/test/java/com/tngtech/jgiven/testframework/TestExecutor.java +++ b/jgiven-tests/src/test/java/com/tngtech/jgiven/testframework/TestExecutor.java @@ -1,22 +1,25 @@ package com.tngtech.jgiven.testframework; import com.tngtech.jgiven.junit.JUnitExecutor; +import com.tngtech.jgiven.junit5.JUnit5Executor; import com.tngtech.jgiven.testng.TestNgExecutor; public abstract class TestExecutor { - public static TestExecutor getExecutor( TestFramework framework ) { - switch( framework ) { + public static TestExecutor getExecutor(TestFramework framework) { + switch (framework) { case JUnit: return new JUnitExecutor(); + case JUnit5: + return new JUnit5Executor(); case TestNG: return new TestNgExecutor(); default: - throw new IllegalArgumentException( "Unknown framework: " + framework ); + throw new IllegalArgumentException("Unknown framework: " + framework); } } - public abstract TestExecutionResult execute( Class testClass, String testMethod ); + public abstract TestExecutionResult execute(Class testClass, String testMethod); - public abstract TestExecutionResult execute( Class testClass ); + public abstract TestExecutionResult execute(Class testClass); } diff --git a/jgiven-tests/src/test/java/com/tngtech/jgiven/testframework/TestFramework.java b/jgiven-tests/src/test/java/com/tngtech/jgiven/testframework/TestFramework.java index 3082a31eae..4423e4eaa9 100644 --- a/jgiven-tests/src/test/java/com/tngtech/jgiven/testframework/TestFramework.java +++ b/jgiven-tests/src/test/java/com/tngtech/jgiven/testframework/TestFramework.java @@ -2,5 +2,6 @@ public enum TestFramework { JUnit, + JUnit5, TestNG -} \ No newline at end of file +} diff --git a/jgiven-tests/src/test/java/com/tngtech/jgiven/testframework/TestFrameworkExecutionTest.java b/jgiven-tests/src/test/java/com/tngtech/jgiven/testframework/TestFrameworkExecutionTest.java index ce856ffd89..1c198c6de8 100644 --- a/jgiven-tests/src/test/java/com/tngtech/jgiven/testframework/TestFrameworkExecutionTest.java +++ b/jgiven-tests/src/test/java/com/tngtech/jgiven/testframework/TestFrameworkExecutionTest.java @@ -1,36 +1,37 @@ package com.tngtech.jgiven.testframework; -import java.util.Arrays; - -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; -import org.junit.runners.Parameterized.Parameters; - import com.tngtech.jgiven.GivenScenarioTest; import com.tngtech.jgiven.JGivenScenarioTest; import com.tngtech.jgiven.tags.FeatureJUnit; +import com.tngtech.jgiven.tags.FeatureJUnit5; import com.tngtech.jgiven.tags.FeaturePending; import com.tngtech.jgiven.tags.FeatureTags; import com.tngtech.jgiven.tags.FeatureTestNg; import com.tngtech.jgiven.tags.Issue; +import java.util.Arrays; +import java.util.stream.Collectors; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameters; +@FeatureJUnit5 @FeatureJUnit @FeatureTestNg -@RunWith( Parameterized.class ) -public class TestFrameworkExecutionTest extends JGivenScenarioTest, WhenTestFramework, ThenTestFramework> { +@RunWith(Parameterized.class) +public class TestFrameworkExecutionTest + extends JGivenScenarioTest, WhenTestFramework, ThenTestFramework> { public final TestFramework testFramework; @Parameters public static Iterable testFrameworks() { - return Arrays.asList( new Object[][] { - { TestFramework.JUnit }, - { TestFramework.TestNG }, - } ); + return Arrays.stream(TestFramework.values()) + .map(value -> new Object[] {value}) + .collect(Collectors.toList()); } - public TestFrameworkExecutionTest( TestFramework testFramework ) { + public TestFrameworkExecutionTest(TestFramework testFramework) { this.testFramework = testFramework; } @@ -39,7 +40,7 @@ public TestFrameworkExecutionTest( TestFramework testFramework ) { public void failing_tests_annotated_with_Pending_are_ignored() { given().a_failing_test() .and().the_test_is_annotated_with_Pending(); - when().the_test_is_executed_with( testFramework ); + when().the_test_is_executed_with(testFramework); then().the_test_is_ignored(); } @@ -48,29 +49,30 @@ public void failing_tests_annotated_with_Pending_are_ignored() { public void passing_tests_annotated_with_Pending_are_ignored() { given().a_passing_test() .and().the_test_is_annotated_with_Pending(); - when().the_test_is_executed_with( testFramework ); + when().the_test_is_executed_with(testFramework); then().the_test_is_ignored(); } @Test - @Issue( "#4" ) + @Issue("#4") @FeaturePending public void passing_tests_annotated_with_Pending_with_failIfPassed_set_to_true_fail() { given().a_passing_test() .and().the_test_is_annotated_with_Pending() .with().failIfPassed_set_to_true(); - when().the_test_is_executed_with( testFramework ); - then().the_test_fails_with_message( "Test succeeded, but failIfPassed set to true. Now might be the right time to remove the @Pending annotation." ); + when().the_test_is_executed_with(testFramework); + then().the_test_fails_with_message( + "Test succeeded, but failIfPassed set to true. Now might be the right time to remove the @Pending annotation."); } @Test - @Issue( "#4" ) + @Issue("#4") @FeaturePending public void failing_tests_annotated_with_Pending_with_failIfPassed_set_to_true_are_ignored() { given().a_failing_test() .and().the_test_is_annotated_with_Pending() .with().failIfPassed_set_to_true(); - when().the_test_is_executed_with( testFramework ); + when().the_test_is_executed_with(testFramework); then().the_test_is_ignored(); } @@ -80,23 +82,23 @@ public void failing_tests_annotated_with_Pending_with_executeSteps_set_to_true_a given().a_failing_test() .and().the_test_is_annotated_with_Pending() .with().executeSteps_set_to_true(); - when().the_test_is_executed_with( testFramework ); + when().the_test_is_executed_with(testFramework); then().the_test_is_ignored(); } @Test public void passing_steps_before_failing_steps_are_reported_as_passed() { - given().a_failing_test_with_$_steps( 2 ) - .and().step_$_fails( 2 ); - when().the_test_is_executed_with( testFramework ); - then().step_$_is_reported_as_passed( 1 ) - .and().step_$_is_reported_as_failed( 2 ); + given().a_failing_test_with_$_steps(2) + .and().step_$_fails(2); + when().the_test_is_executed_with(testFramework); + then().step_$_is_reported_as_passed(1) + .and().step_$_is_reported_as_failed(2); } @Test public void the_error_message_of_a_failing_step_is_reported() { given().a_failing_test(); - when().the_test_is_executed_with( testFramework ); + when().the_test_is_executed_with(testFramework); then().the_case_is_marked_as_failed() .and().an_error_message_is_stored_in_the_report(); } @@ -105,16 +107,16 @@ public void the_error_message_of_a_failing_step_is_reported() { @FeatureTags public void tag_annotations_appear_in_the_report_model() { given().a_test() - .and().the_test_has_a_tag_annotation_named( "TestTag" ); - when().the_test_is_executed_with( testFramework ); - then().the_report_model_contains_a_tag_named( "com.tngtech.jgiven.tests.TestTag" ); + .and().the_test_has_a_tag_annotation_named("TestTag"); + when().the_test_is_executed_with(testFramework); + then().the_report_model_contains_a_tag_named("com.tngtech.jgiven.tests.TestTag"); } @Test public void description_annotations_on_test_classes_are_evaluated() { given().a_test_class() - .and().the_test_class_has_a_description_annotation_with_value( "Test Description" ); - when().the_test_is_executed_with( testFramework ); - then().the_description_of_the_report_model_is( "Test Description" ); + .and().the_test_class_has_a_description_annotation_with_value("Test Description"); + when().the_test_is_executed_with(testFramework); + then().the_description_of_the_report_model_is("Test Description"); } } diff --git a/jgiven-tests/src/test/java/com/tngtech/jgiven/testframework/ThenTestFramework.java b/jgiven-tests/src/test/java/com/tngtech/jgiven/testframework/ThenTestFramework.java index 6a5b2c005a..785bb4af48 100644 --- a/jgiven-tests/src/test/java/com/tngtech/jgiven/testframework/ThenTestFramework.java +++ b/jgiven-tests/src/test/java/com/tngtech/jgiven/testframework/ThenTestFramework.java @@ -29,53 +29,54 @@ public SELF the_test_is_ignored() { } public SELF the_test_passes() { - assertThat( result.getFailureCount() ).as( "failure count" ).isEqualTo( 0 ); + assertThat(result.getFailureCount()).as("failure count").isEqualTo(0); return self(); } - public SELF $_tests_fail( int nFailedTests ) { - assertThat( result.getFailureCount() ).isEqualTo( nFailedTests ); + public SELF $_tests_fail(int nFailedTests) { + assertThat(result.getFailureCount()).isEqualTo(nFailedTests); return self(); } public SELF the_test_fails() { - assertThat( result.getFailureCount() ).as( "failure count" ).isGreaterThan( 0 ); + assertThat(result.getFailureCount()).as("failure count").isGreaterThan(0); return self(); } - public void the_test_fails_with_message( String expectedMessage ) { + public SELF the_test_fails_with_message(String expectedMessage) { the_test_fails(); - assertThat( result.getFailureMessage( 0 ) ).as( "failure message" ).isEqualTo( expectedMessage ); + assertThat(result.getFailureMessage(0)).as("failure message").contains(expectedMessage); + return self(); } public SELF the_report_model_contains_one_scenario_for_each_test_method() { Method[] declaredMethods = testScenario.testClass.getDeclaredMethods(); - List nonStaticMethods = ReflectionUtil.getNonStaticMethod( testScenario.testClass.getDeclaredMethods() ); - assertThat( reportModel.getScenarios() ).hasSize( nonStaticMethods.size() ); + List nonStaticMethods = ReflectionUtil.getNonStaticMethod(testScenario.testClass.getDeclaredMethods()); + assertThat(reportModel.getScenarios()).hasSize(nonStaticMethods.size()); return self(); } - public SELF each_scenario_contains_$_cases( int nParameters ) { - for( ScenarioModel scenario : reportModel.getScenarios() ) { - assertThat( scenario.getScenarioCases() ).hasSize( nParameters ); + public SELF each_scenario_contains_$_cases(int nParameters) { + for (ScenarioModel scenario : reportModel.getScenarios()) { + assertThat(scenario.getScenarioCases()).hasSize(nParameters); } return self(); } public SELF has_a_valid_class_name_if_it_is_not_null() { - if( reportModel != null ) { + if (reportModel != null) { the_report_model_has_a_valid_class_name(); } return self(); } public SELF the_report_model_has_a_valid_class_name() { - assertThat( reportModel.getClassName() ).isEqualTo( testScenario.testClass.getName() ); + assertThat(reportModel.getClassName()).isEqualTo(testScenario.testClass.getName()); return self(); } - public SELF the_scenario_has_execution_status( ExecutionStatus status ) { - assertThat( reportModel.getLastScenarioModel().getExecutionStatus() ).isEqualTo( status ); + public SELF the_scenario_has_execution_status(ExecutionStatus status) { + assertThat(reportModel.getLastScenarioModel().getExecutionStatus()).isEqualTo(status); return self(); } } diff --git a/jgiven-tests/src/test/java/com/tngtech/jgiven/testframework/WhenTestFramework.java b/jgiven-tests/src/test/java/com/tngtech/jgiven/testframework/WhenTestFramework.java index d7652cf071..9d608d7624 100644 --- a/jgiven-tests/src/test/java/com/tngtech/jgiven/testframework/WhenTestFramework.java +++ b/jgiven-tests/src/test/java/com/tngtech/jgiven/testframework/WhenTestFramework.java @@ -22,34 +22,42 @@ public class WhenTestFramework> extends Stage< @ProvidedScenarioState private TestExecutionResult testExecutionResult; - public SELF the_test_is_executed_with( TestFramework framework ) { - Assertions.assertThat( testScenario ).as( "No matching test scenario found" ).isNotNull(); + public SELF the_test_is_executed_with(TestFramework framework) { + Assertions.assertThat(testScenario).as("No matching test scenario found").isNotNull(); - executor = TestExecutor.getExecutor( framework ); - testExecutionResult = executor.execute( testScenario.testClass, testScenario.testMethod ); + executor = TestExecutor.getExecutor(framework); + testExecutionResult = executor.execute(testScenario.testClass, testScenario.testMethod); reportModel = testExecutionResult.getReportModel(); return self(); } - public SELF the_test_class_is_executed_with( TestFramework framework ) { - Assertions.assertThat( testScenario ).as( "No matching test scenario found" ).isNotNull(); + public SELF the_test_class_is_executed_with(TestFramework framework) { + Assertions.assertThat(testScenario).as("No matching test scenario found").isNotNull(); - executor = TestExecutor.getExecutor( framework ); - testExecutionResult = executor.execute( testScenario.testClass ); + executor = TestExecutor.getExecutor(framework); + testExecutionResult = executor.execute(testScenario.testClass); reportModel = testExecutionResult.getReportModel(); return self(); } public SELF the_test_is_executed_with_JUnit() { - return the_test_is_executed_with( TestFramework.JUnit ); + return the_test_is_executed_with(TestFramework.JUnit); + } + + public SELF the_test_is_executed_with_JUnit5() { + return the_test_is_executed_with(TestFramework.JUnit5); } public SELF the_test_is_executed_with_TestNG() { - return the_test_is_executed_with( TestFramework.TestNG ); + return the_test_is_executed_with(TestFramework.TestNG); } public SELF the_test_class_is_executed_with_JUnit() { - return the_test_class_is_executed_with( TestFramework.JUnit ); + return the_test_class_is_executed_with(TestFramework.JUnit); + } + + public SELF the_test_class_is_executed_with_JUnit5() { + return the_test_class_is_executed_with(TestFramework.JUnit5); } } diff --git a/jgiven-tests/src/test/java/com/tngtech/jgiven/testng/TestNgExecutor.java b/jgiven-tests/src/test/java/com/tngtech/jgiven/testng/TestNgExecutor.java index b9af087ce7..4b579133cc 100644 --- a/jgiven-tests/src/test/java/com/tngtech/jgiven/testng/TestNgExecutor.java +++ b/jgiven-tests/src/test/java/com/tngtech/jgiven/testng/TestNgExecutor.java @@ -37,7 +37,7 @@ public TestExecutionResult execute( Class testClass, String testMethod ) { } @Override - public TestExecutionResult execute( Class testClass ) { + public TestExecutionResult execute(Class testClass ) { return execute( testClass, null ); } From 083d7fbdd1bdd53e88eb9fa9181cc85e093bade1 Mon Sep 17 00:00:00 2001 From: l-1sqared <30831153+l-1squared@users.noreply.github.com> Date: Wed, 2 Feb 2022 06:51:43 +0100 Subject: [PATCH 2/3] Add fluent interface to test stages Signed-off-by: l-1sqared <30831153+l-1squared@users.noreply.github.com> --- .../main/java/com/tngtech/jgiven/tests/GivenTestStage.java | 6 ++++-- .../main/java/com/tngtech/jgiven/tests/WhenTestStage.java | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/jgiven-tests/src/main/java/com/tngtech/jgiven/tests/GivenTestStage.java b/jgiven-tests/src/main/java/com/tngtech/jgiven/tests/GivenTestStage.java index b180fe75e5..198ee9d1bf 100644 --- a/jgiven-tests/src/main/java/com/tngtech/jgiven/tests/GivenTestStage.java +++ b/jgiven-tests/src/main/java/com/tngtech/jgiven/tests/GivenTestStage.java @@ -7,13 +7,15 @@ public GivenTestStage an_exception_is_thrown() { throw new RuntimeException("Some Exception"); } - public void nothing() { + public GivenTestStage nothing() { + return this; } - public void a_failed_step(boolean fail) { + public GivenTestStage a_failed_step(boolean fail) { if (fail) { throw new IllegalArgumentException(); } + return this; } diff --git a/jgiven-tests/src/main/java/com/tngtech/jgiven/tests/WhenTestStage.java b/jgiven-tests/src/main/java/com/tngtech/jgiven/tests/WhenTestStage.java index 24f4810931..8a46fe0adb 100644 --- a/jgiven-tests/src/main/java/com/tngtech/jgiven/tests/WhenTestStage.java +++ b/jgiven-tests/src/main/java/com/tngtech/jgiven/tests/WhenTestStage.java @@ -8,7 +8,7 @@ public WhenTestStage something_happens() { return self(); } - public void a_step_fails() { + public WhenTestStage a_step_fails() { throw new AssertionError( "assertion failed in test step" ); } From 1bc9d4aca5307ca93608c762590875726b2a9398 Mon Sep 17 00:00:00 2001 From: l-1sqared <30831153+l-1squared@users.noreply.github.com> Date: Thu, 10 Feb 2022 08:31:06 +0100 Subject: [PATCH 3/3] Prohibit usage of JUnit5 class lifecycle JUnit5 offers to use the same class instance over several methods. This however is not in line with the scenario concept of JGiven. The JGivenExtension removes the scenario after the exection of each state, resulting in failure if the next scenario is executed on the same instance Moreover, it is unclear how scenarios that share the same instance react when these methods are executed in parallel. In addition, the JUnit 5 testframework tests were adapted. It turns out that when JUnit5 fails on the class level it returns a successful report model for method but a failure for the class level. Signed-off-by: l-1sqared <30831153+l-1squared@users.noreply.github.com> --- docs/junit5.adoc | 1 + .../jgiven/junit5/JGivenExtension.java | 43 +++-- .../jgiven/tests/TestScenarioRepository.java | 150 +++++++++--------- .../tests/TestWithPerClassLifecycle.java | 16 ++ .../com/tngtech/jgiven/GivenScenarioTest.java | 5 + .../jgiven/junit5/JUnit5ExecutorTest.java | 7 + .../junit5/Junit5TestExecutionResult.java | 49 ++++-- .../junit5/TestExecutionResultProvider.java | 25 ++- 8 files changed, 191 insertions(+), 105 deletions(-) create mode 100644 jgiven-tests/src/main/java/com/tngtech/jgiven/tests/TestWithPerClassLifecycle.java diff --git a/docs/junit5.adoc b/docs/junit5.adoc index 3b50ad111e..d3b9ef78ce 100644 --- a/docs/junit5.adoc +++ b/docs/junit5.adoc @@ -74,6 +74,7 @@ public class JGiven5ScenarioTest } } ---- +Please note that JGiven is built around each scenario, i.e. test method, having its own test instance. Therefore, we cannot support JUnit's per-class test instance lifecycle. An exception will be thrown if any such attempt is made. === Example Project diff --git a/jgiven-junit5/src/main/java/com/tngtech/jgiven/junit5/JGivenExtension.java b/jgiven-junit5/src/main/java/com/tngtech/jgiven/junit5/JGivenExtension.java index cd54b1eb34..dd32c2043a 100644 --- a/jgiven-junit5/src/main/java/com/tngtech/jgiven/junit5/JGivenExtension.java +++ b/jgiven-junit5/src/main/java/com/tngtech/jgiven/junit5/JGivenExtension.java @@ -3,20 +3,25 @@ import static com.tngtech.jgiven.report.model.ExecutionStatus.FAILED; import static com.tngtech.jgiven.report.model.ExecutionStatus.SUCCESS; -import java.util.EnumSet; - -import org.junit.jupiter.api.Assumptions; -import org.junit.jupiter.api.Tag; -import org.junit.jupiter.api.extension.*; -import org.junit.jupiter.api.extension.ExtensionContext.Namespace; - import com.tngtech.jgiven.base.ScenarioTestBase; import com.tngtech.jgiven.config.AbstractJGivenConfiguration; import com.tngtech.jgiven.config.ConfigurationUtil; +import com.tngtech.jgiven.exception.JGivenWrongUsageException; import com.tngtech.jgiven.impl.ScenarioBase; import com.tngtech.jgiven.impl.ScenarioHolder; import com.tngtech.jgiven.report.impl.CommonReportHelper; import com.tngtech.jgiven.report.model.ReportModel; +import java.util.EnumSet; +import org.junit.jupiter.api.Assumptions; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.extension.AfterAllCallback; +import org.junit.jupiter.api.extension.AfterTestExecutionCallback; +import org.junit.jupiter.api.extension.BeforeAllCallback; +import org.junit.jupiter.api.extension.BeforeEachCallback; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.api.extension.ExtensionContext.Namespace; +import org.junit.jupiter.api.extension.TestInstancePostProcessor; /** * This extension enables JGiven for JUnit 5 Tests. @@ -47,6 +52,8 @@ public class JGivenExtension implements @Override public void beforeAll(ExtensionContext context) { + validatePerMethodLifecycle(context); + ReportModel reportModel = new ReportModel(); reportModel.setTestClass(context.getTestClass().get()); if (!context.getDisplayName().equals(context.getTestClass().get().getSimpleName())) { @@ -62,6 +69,7 @@ public void beforeAll(ExtensionContext context) { } } + @Override public void afterAll(ExtensionContext context) { ScenarioHolder.get().removeScenarioOfCurrentThread(); @@ -96,10 +104,6 @@ public void afterTestExecution(ExtensionContext context) throws Exception { } } - private ScenarioBase getScenario() { - return ScenarioHolder.get().getScenarioOfCurrentThread(); - } - @Override public void postProcessTestInstance(Object testInstance, ExtensionContext context) { ScenarioBase currentScenario = ScenarioHolder.get().getScenarioOfCurrentThread(); @@ -125,4 +129,21 @@ public void postProcessTestInstance(Object testInstance, ExtensionContext contex scenario.getExecutor().readScenarioState(testInstance); } + private void validatePerMethodLifecycle(ExtensionContext context) { + if (isPerClassLifecycle(context)) { + throw new JGivenWrongUsageException( + "JGiven does not support keeping a test instance over multiple scenarios. Please use Lifecycle '" + + TestInstance.Lifecycle.PER_METHOD + "'."); + } + } + + private boolean isPerClassLifecycle(ExtensionContext context) { + return context.getTestInstanceLifecycle() + .filter(lifecycle -> TestInstance.Lifecycle.PER_CLASS == lifecycle) + .isPresent(); + } + + private ScenarioBase getScenario() { + return ScenarioHolder.get().getScenarioOfCurrentThread(); + } } diff --git a/jgiven-tests/src/main/java/com/tngtech/jgiven/tests/TestScenarioRepository.java b/jgiven-tests/src/main/java/com/tngtech/jgiven/tests/TestScenarioRepository.java index 40eda3ee6a..ba158ff5fe 100644 --- a/jgiven-tests/src/main/java/com/tngtech/jgiven/tests/TestScenarioRepository.java +++ b/jgiven-tests/src/main/java/com/tngtech/jgiven/tests/TestScenarioRepository.java @@ -7,6 +7,7 @@ public class TestScenarioRepository { + public static class SearchCriteria { public boolean pending = false; public boolean failing = false; @@ -21,53 +22,53 @@ public static class SearchCriteria { public Integer numberOfParameters; public String testClassDescription; - public boolean matches( ScenarioCriteria criteria ) { - if( pending != criteria.pending) { + public boolean matches(ScenarioCriteria criteria) { + if (pending != criteria.pending) { return false; } - if( failIfPassed != null && !failIfPassed.equals( criteria.failIfPassed ) ) { + if (failIfPassed != null && !failIfPassed.equals(criteria.failIfPassed)) { return false; } - if( executeSteps != null && !executeSteps.equals( criteria.executeSteps ) ) { + if (executeSteps != null && !executeSteps.equals(criteria.executeSteps)) { return false; } - if( failing != criteria.failing ) { + if (failing != criteria.failing) { return false; } - if( numberOfSteps != null && !numberOfSteps.equals( criteria.numberOfSteps ) ) { + if (numberOfSteps != null && !numberOfSteps.equals(criteria.numberOfSteps)) { return false; } - if( numberOfFailingStages != null && !numberOfFailingStages.equals( criteria.numberOfFailingStages ) ) { + if (numberOfFailingStages != null && !numberOfFailingStages.equals(criteria.numberOfFailingStages)) { return false; } - if( failingStep != null && !failingStep.equals( criteria.failingStep ) ) { + if (failingStep != null && !failingStep.equals(criteria.failingStep)) { return false; } - if( stageWithFailingAfterStageMethod != null - && !stageWithFailingAfterStageMethod.equals( criteria.stageWithFailingAfterStageMethod ) ) { + if (stageWithFailingAfterStageMethod != null + && !stageWithFailingAfterStageMethod.equals(criteria.stageWithFailingAfterStageMethod)) { return false; } - if( tagAnnotation != null && !tagAnnotation.equals( criteria.tagAnnotation ) ) { + if (tagAnnotation != null && !tagAnnotation.equals(criteria.tagAnnotation)) { return false; } - if( parameterizedRunner != null && !parameterizedRunner.equals( criteria.parameterizedRunner ) ) { + if (parameterizedRunner != null && !parameterizedRunner.equals(criteria.parameterizedRunner)) { return false; } - if( numberOfParameters != null && !numberOfParameters.equals(criteria.numberOfParameters) ) { + if (numberOfParameters != null && !numberOfParameters.equals(criteria.numberOfParameters)) { return false; } - if( testClassDescription != null && !testClassDescription.equals( criteria.testClassDescription ) ) { + if (testClassDescription != null && !testClassDescription.equals(criteria.testClassDescription)) { return false; } @@ -109,13 +110,13 @@ public ScenarioCriteria failing() { return this; } - public ScenarioCriteria failingStep( int i ) { + public ScenarioCriteria failingStep(int i) { failing(); failingStep = i; return this; } - public ScenarioCriteria numberOfSteps( int n ) { + public ScenarioCriteria numberOfSteps(int n) { numberOfSteps = n; return this; } @@ -125,17 +126,17 @@ public ScenarioCriteria tagAnnotation() { return this; } - public ScenarioCriteria numberOfFailingStages( int i ) { + public ScenarioCriteria numberOfFailingStages(int i) { numberOfFailingStages = i; return this; } - public ScenarioCriteria stageWithFailingAfterStageMethod( Integer stageWithFailingAfterStageMethod ) { + public ScenarioCriteria stageWithFailingAfterStageMethod(Integer stageWithFailingAfterStageMethod) { this.stageWithFailingAfterStageMethod = stageWithFailingAfterStageMethod; return this; } - public ScenarioCriteria numberOfParameters( int n ) { + public ScenarioCriteria numberOfParameters(int n) { this.numberOfParameters = n; return this; } @@ -145,7 +146,7 @@ public ScenarioCriteria parameterizedRunner() { return this; } - public ScenarioCriteria testClassDescription( String value ) { + public ScenarioCriteria testClassDescription(String value) { this.testClassDescription = value; return this; } @@ -156,16 +157,16 @@ public static class TestScenario { public String testMethod; public ScenarioCriteria criteria = new ScenarioCriteria(); - public TestScenario( Class testClass ) { + public TestScenario(Class testClass) { this.testClass = testClass; } - public TestScenario( String testMethod ) { + public TestScenario(String testMethod) { this.testMethod = testMethod; this.testClass = TestScenarios.class; } - public TestScenario( Class testClass, String testMethod ) { + public TestScenario(Class testClass, String testMethod) { this.testClass = testClass; this.testMethod = testMethod; } @@ -173,113 +174,116 @@ public TestScenario( Class testClass, String testMethod ) { final static List testScenarios = setupTestScenarios(); - public static TestScenario findScenario( SearchCriteria searchCriteria ) { - for( TestScenario scenario : testScenarios ) { - if( searchCriteria.matches( scenario.criteria ) ) { + public static TestScenario findScenario(SearchCriteria searchCriteria) { + for (TestScenario scenario : testScenarios) { + if (searchCriteria.matches(scenario.criteria)) { return scenario; } } - throw new IllegalArgumentException( "No matching scenario found" ); + throw new IllegalArgumentException("No matching scenario found"); } - private static ScenarioCriteria addTestScenario( List list, Class testClass ) { - TestScenario testScenario = new TestScenario( testClass ); - list.add( testScenario ); + private static ScenarioCriteria addTestScenario(List list, Class testClass) { + TestScenario testScenario = new TestScenario(testClass); + list.add(testScenario); return testScenario.criteria; } - private static ScenarioCriteria addTestScenario( List list, String testMethod ) { - TestScenario testScenario = new TestScenario( testMethod ); - list.add( testScenario ); + private static ScenarioCriteria addTestScenario(List list, String testMethod) { + TestScenario testScenario = new TestScenario(testMethod); + list.add(testScenario); return testScenario.criteria; } - private static ScenarioCriteria addTestScenario( List list, Class testClass, String testMethod ) { - TestScenario testScenario = new TestScenario( testClass ); + private static ScenarioCriteria addTestScenario(List list, Class testClass, String testMethod) { + TestScenario testScenario = new TestScenario(testClass); testScenario.testMethod = testMethod; - list.add( testScenario ); + list.add(testScenario); return testScenario.criteria; } private static List setupTestScenarios() { List result = Lists.newArrayList(); - addTestScenario( result, "failing_test_with_two_steps" ) - .numberOfSteps( 2 ) - .failingStep( 1 ); + addTestScenario(result, "failing_test_with_two_steps") + .numberOfSteps(2) + .failingStep(1); - addTestScenario( result, "failing_test_with_three_steps" ) - .numberOfSteps( 3 ) - .failingStep( 1 ); + addTestScenario(result, "failing_test_with_three_steps") + .numberOfSteps(3) + .failingStep(1); - addTestScenario( result, "failing_test_with_two_steps_and_second_step_fails" ) - .numberOfSteps( 2 ) - .failingStep( 2 ); + addTestScenario(result, "failing_test_with_two_steps_and_second_step_fails") + .numberOfSteps(2) + .failingStep(2); - addTestScenario( result, "failing_test_with_two_failing_stages" ) - .numberOfSteps( 2 ) - .numberOfFailingStages( 2 ) - .failingStep( 1 ); + addTestScenario(result, "failing_test_with_two_failing_stages") + .numberOfSteps(2) + .numberOfFailingStages(2) + .failingStep(1); - addTestScenario( result, "failing_test_where_second_stage_has_a_failing_after_stage_method" ) - .numberOfSteps( 2 ) - .numberOfFailingStages( 2 ) - .stageWithFailingAfterStageMethod( 2 ) - .failingStep( 1 ); + addTestScenario(result, "failing_test_where_second_stage_has_a_failing_after_stage_method") + .numberOfSteps(2) + .numberOfFailingStages(2) + .stageWithFailingAfterStageMethod(2) + .failingStep(1); - addTestScenario( result, "failing_test_with_Pending_annotation" ) + addTestScenario(result, "failing_test_with_Pending_annotation") .pending() - .numberOfSteps( 2 ) - .failingStep( 1 ); + .numberOfSteps(2) + .failingStep(1); - addTestScenario( result, "passing_test_with_Pending_annotation" ) + addTestScenario(result, "passing_test_with_Pending_annotation") .pending(); - addTestScenario( result, "passing_test_with_Pending_annotation_and_failIfPassed_set_to_true" ) + addTestScenario(result, "passing_test_with_Pending_annotation_and_failIfPassed_set_to_true") .pending() .failIfPassed(); - addTestScenario( result, "failing_test_with_Pending_annotation_and_failIfPassed_set_to_true" ) + addTestScenario(result, "failing_test_with_Pending_annotation_and_failIfPassed_set_to_true") .pending() .failIfPassed() - .failingStep( 1 ); + .failingStep(1); - addTestScenario( result, "failing_test_with_Pending_annotation_and_executeSteps_set_to_true" ) + addTestScenario(result, "failing_test_with_Pending_annotation_and_executeSteps_set_to_true") .pending() .executeSteps() - .failingStep( 1 ); + .failingStep(1); - addTestScenario( result, "test_with_tag_annotation" ) + addTestScenario(result, "test_with_tag_annotation") .tagAnnotation(); - addTestScenario( result, TestClassWithParameterizedRunner.class ) + addTestScenario(result, TestClassWithParameterizedRunner.class) .parameterizedRunner() - .numberOfParameters( 2 ); + .numberOfParameters(2); - addTestScenario( result, TestClassWithDescription.class, "some_test" ) - .testClassDescription( TestClassWithDescription.class.getAnnotation( Description.class ).value() ); + addTestScenario(result, TestClassWithDescription.class, "some_test") + .testClassDescription(TestClassWithDescription.class.getAnnotation(Description.class).value()); return result; } public static TestScenario testClassWithOnlyIgnoredTests() { - return new TestScenario( TestClassWithOnlyIgnoredTests.class ); + return new TestScenario(TestClassWithOnlyIgnoredTests.class); } public static TestScenario testClassWithAFailingScenarioAndAFailingAfterStage() { - return new TestScenario( TestWithExceptionsInAfterMethod.class ); + return new TestScenario(TestWithExceptionsInAfterMethod.class); } public static TestScenario testWithTwoCasesAndTheFirstOneFails() { - return new TestScenario( TestWithTwoCasesAndAFailingOne.class ); + return new TestScenario(TestWithTwoCasesAndAFailingOne.class); } - public static TestScenario junit5TestsWithModificationsInAfterMethod(){ + public static TestScenario junit5TestsWithModificationsInAfterMethod() { return new TestScenario(JUnit5AfterMethodTests.class); } public static TestScenario testNgTestWithAFailingCase() { - return new TestScenario( FailingCasesTestNgTest.class ); + return new TestScenario(FailingCasesTestNgTest.class); } + public static TestScenario junit5TestClassWithPerClassLifecycle() { + return new TestScenario(TestWithPerClassLifecycle.class); + } } diff --git a/jgiven-tests/src/main/java/com/tngtech/jgiven/tests/TestWithPerClassLifecycle.java b/jgiven-tests/src/main/java/com/tngtech/jgiven/tests/TestWithPerClassLifecycle.java new file mode 100644 index 0000000000..c82d81748b --- /dev/null +++ b/jgiven-tests/src/main/java/com/tngtech/jgiven/tests/TestWithPerClassLifecycle.java @@ -0,0 +1,16 @@ +package com.tngtech.jgiven.tests; + + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; + +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +class TestWithPerClassLifecycle extends ScenarioTestForTesting { + + @Test + void innocuous_jgiven_test() { + given().nothing(); + when().something_happens(); + then().something_happened(); + } +} diff --git a/jgiven-tests/src/test/java/com/tngtech/jgiven/GivenScenarioTest.java b/jgiven-tests/src/test/java/com/tngtech/jgiven/GivenScenarioTest.java index e70cd92743..f65cab5f07 100644 --- a/jgiven-tests/src/test/java/com/tngtech/jgiven/GivenScenarioTest.java +++ b/jgiven-tests/src/test/java/com/tngtech/jgiven/GivenScenarioTest.java @@ -132,4 +132,9 @@ public SELF junit5_tests_with_scenario_modifications_in_after_method() { testScenario = TestScenarioRepository.junit5TestsWithModificationsInAfterMethod(); return self(); } + + public SELF junit5_test_class_with_a_per_class_lifecycle(){ + testScenario = TestScenarioRepository.junit5TestClassWithPerClassLifecycle(); + return self(); + } } diff --git a/jgiven-tests/src/test/java/com/tngtech/jgiven/junit5/JUnit5ExecutorTest.java b/jgiven-tests/src/test/java/com/tngtech/jgiven/junit5/JUnit5ExecutorTest.java index 0e08ce5137..783827b185 100644 --- a/jgiven-tests/src/test/java/com/tngtech/jgiven/junit5/JUnit5ExecutorTest.java +++ b/jgiven-tests/src/test/java/com/tngtech/jgiven/junit5/JUnit5ExecutorTest.java @@ -20,6 +20,13 @@ public void tests_with_scenario_modifications_in_after_method() { then().each_scenario_contains_$_cases(2); } + @Test + public void tests_with_a_per_class_lifecycle() { + given().junit5_test_class_with_a_per_class_lifecycle(); + when().the_test_class_is_executed_with_JUnit5(); + then().the_test_fails_with_message("JGiven does not support keeping a test instance over multiple scenarios"); + } + @Test @Issue("#25") public void a_valid_report_is_generated_for_classes_that_are_disabled() { diff --git a/jgiven-tests/src/test/java/com/tngtech/jgiven/junit5/Junit5TestExecutionResult.java b/jgiven-tests/src/test/java/com/tngtech/jgiven/junit5/Junit5TestExecutionResult.java index aec1634c0b..9db7f16434 100644 --- a/jgiven-tests/src/test/java/com/tngtech/jgiven/junit5/Junit5TestExecutionResult.java +++ b/jgiven-tests/src/test/java/com/tngtech/jgiven/junit5/Junit5TestExecutionResult.java @@ -4,35 +4,39 @@ import com.tngtech.jgiven.testframework.TestExecutionResult; import java.lang.reflect.Method; import java.util.Map; +import java.util.stream.Stream; class Junit5TestExecutionResult extends TestExecutionResult { private final ReportModel report; - private final Map result; + private final Map methodResults; + private final Map, org.junit.platform.engine.TestExecutionResult> classResults; public Junit5TestExecutionResult(ReportModel report, - Map result) { + Map result, + Map, org.junit.platform.engine.TestExecutionResult> classResults) { this.report = report; - this.result = result; + this.methodResults = result; + this.classResults = classResults; } + @Override + public ReportModel getReportModel() { + return report; + } + @Override public int getFailureCount() { - return (int) result.values().stream() + return (int) Stream.concat(methodResults.values().stream(), classResults.values().stream()) .filter(result -> result.getStatus() == org.junit.platform.engine.TestExecutionResult.Status.FAILED) .count(); } @Override public String getFailureMessage(int i) { - if (result.size() == 1 && i == 0) { - return result.values().stream() - .findFirst() - .flatMap(org.junit.platform.engine.TestExecutionResult::getThrowable) - .map(Throwable::getMessage) - .orElseThrow(() -> new IllegalStateException("Expected failure message for test 1, but none present.")); - + if (i == 0 && getFailureCount() == 1) { + return assumeCallerRequestsTheOnlyFailureThatExists(); } else { throw new UnsupportedOperationException("Cannot address a map by Index." + "This method is a result of attempting to adapt to data model that was focused on JUnit4. " @@ -40,8 +44,25 @@ public String getFailureMessage(int i) { } } - @Override - public ReportModel getReportModel() { - return report; + private String assumeCallerRequestsTheOnlyFailureThatExists() { + if (noFailureInClassResults()) { + return getFailureMessageFromTestResults(methodResults); + } else { + return getFailureMessageFromTestResults(classResults); + } + } + + private boolean noFailureInClassResults() { + return classResults.values().stream() + .map(org.junit.platform.engine.TestExecutionResult::getStatus) + .allMatch(status -> status == org.junit.platform.engine.TestExecutionResult.Status.SUCCESSFUL); + } + + private String getFailureMessageFromTestResults(Map results) { + return results.values().stream() + .findFirst() + .flatMap(org.junit.platform.engine.TestExecutionResult::getThrowable) + .map(Throwable::getMessage) + .orElseThrow(() -> new IllegalStateException("Expected failure message for test 1, but none present.")); } } diff --git a/jgiven-tests/src/test/java/com/tngtech/jgiven/junit5/TestExecutionResultProvider.java b/jgiven-tests/src/test/java/com/tngtech/jgiven/junit5/TestExecutionResultProvider.java index 2417b8775a..3ad53f902e 100644 --- a/jgiven-tests/src/test/java/com/tngtech/jgiven/junit5/TestExecutionResultProvider.java +++ b/jgiven-tests/src/test/java/com/tngtech/jgiven/junit5/TestExecutionResultProvider.java @@ -14,7 +14,8 @@ class TestExecutionResultProvider implements TestExecutionListener { - private Map executionResults = new HashMap<>(); + private final Map methodExecutionResults = new HashMap<>(); + private final Map, org.junit.platform.engine.TestExecutionResult> classExecutionResults = new HashMap<>(); private ReportModel reportModel; @@ -26,22 +27,32 @@ public void executionFinished(TestIdentifier testIdentifier, if (testSource instanceof MethodSource) { handleTestMethodFinished((MethodSource) testSource, testExecutionResult); } else if (testSource instanceof ClassSource) { - handleTestClassFinished((ClassSource) testSource); + handleTestClassFinished((ClassSource) testSource, testExecutionResult); } } - private void handleTestClassFinished(ClassSource testSource) { - this.reportModel = JGivenReportExtractingExtension.getReportModelFor(testSource.getJavaClass()) - .orElseThrow(() -> new IllegalStateException("Failed to obtain report model for tested class.")); + private void handleTestClassFinished(ClassSource testSource, + org.junit.platform.engine.TestExecutionResult testExecutionResult) { + classExecutionResults.put(testSource.getJavaClass(), testExecutionResult); + setReportModel(testSource); + } + + private void setReportModel(ClassSource testSource) { + if (methodExecutionResults.size() == 0) { + this.reportModel = null; + } else { + this.reportModel = JGivenReportExtractingExtension.getReportModelFor(testSource.getJavaClass()) + .orElseThrow(() -> new IllegalStateException("Failed to obtain report model for tested class.")); + } } private void handleTestMethodFinished(MethodSource testSource, org.junit.platform.engine.TestExecutionResult testExecutionResult) { - executionResults.put(testSource.getJavaMethod(), testExecutionResult); + methodExecutionResults.put(testSource.getJavaMethod(), testExecutionResult); } public TestExecutionResult getExecutionResult() { - return new Junit5TestExecutionResult(reportModel, executionResults); + return new Junit5TestExecutionResult(reportModel, methodExecutionResults, classExecutionResults); } }