diff --git a/jgiven-core/src/main/java/com/tngtech/jgiven/impl/util/ReflectionUtil.java b/jgiven-core/src/main/java/com/tngtech/jgiven/impl/util/ReflectionUtil.java index 7fe64fe33d..6c4564c254 100644 --- a/jgiven-core/src/main/java/com/tngtech/jgiven/impl/util/ReflectionUtil.java +++ b/jgiven-core/src/main/java/com/tngtech/jgiven/impl/util/ReflectionUtil.java @@ -213,6 +213,17 @@ public Object apply( Field field ) { }; } + public static Object getFieldValueOrNull(String fieldName, Object target, String errorDescription) { + try { + Field field = target.getClass().getDeclaredField(fieldName); + return getFieldValueOrNull(field, target, errorDescription); + } catch (Exception e) { + log.warn( + format( "Not able to access field '%s'." + errorDescription, fieldName ), e ); + return null; + } + } + public static Object getFieldValueOrNull(Field field, Object target, String errorDescription) { makeAccessible( field, "" ); try { diff --git a/jgiven-junit5/build.gradle b/jgiven-junit5/build.gradle index f03753ce56..df8ce65a41 100644 --- a/jgiven-junit5/build.gradle +++ b/jgiven-junit5/build.gradle @@ -22,9 +22,10 @@ test { dependencies { compile project(':jgiven-core') - compileOnly 'org.junit.jupiter:junit-jupiter-api:5.2.0' + compileOnly 'org.junit.jupiter:junit-jupiter-api:5.3.1' testCompile project(':jgiven-html5-report') - testCompile 'org.junit.jupiter:junit-jupiter-engine:5.2.0' - testCompile 'org.junit.platform:junit-platform-runner:1.2.0' + testCompile 'org.junit.jupiter:junit-jupiter-engine:5.3.1' + testCompile 'org.junit.platform:junit-platform-runner:1.3.1' + testCompile 'org.junit.jupiter:junit-jupiter-params:5.3.1' } diff --git a/jgiven-junit5/src/main/java/com/tngtech/jgiven/junit5/ArgumentReflectionUtil.java b/jgiven-junit5/src/main/java/com/tngtech/jgiven/junit5/ArgumentReflectionUtil.java new file mode 100644 index 0000000000..c373627825 --- /dev/null +++ b/jgiven-junit5/src/main/java/com/tngtech/jgiven/junit5/ArgumentReflectionUtil.java @@ -0,0 +1,55 @@ +package com.tngtech.jgiven.junit5; + +import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import org.junit.jupiter.api.extension.ExtensionContext; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.tngtech.jgiven.impl.util.ParameterNameUtil; +import com.tngtech.jgiven.impl.util.ReflectionUtil; +import com.tngtech.jgiven.report.model.NamedArgument; + +class ArgumentReflectionUtil { + private static final Logger log = LoggerFactory.getLogger( ArgumentReflectionUtil.class ); + + static final String METHOD_EXTENSION_CONTEXT = "org.junit.jupiter.engine.descriptor.MethodExtensionContext"; + static final String TEST_TEMPLATE_INVOCATION_TEST_DESCRIPTOR = "org.junit.jupiter.engine.descriptor.TestTemplateInvocationTestDescriptor"; + static final String PARAMETERIZED_TEST_INVOCATION_CONTEXT = "org.junit.jupiter.params.ParameterizedTestInvocationContext"; + + static final String ERROR = "Not able to access field containing test method arguments. " + + "Probably the internal representation has changed. Consider writing a bug report."; + + /** + * This is a very ugly workaround to get the method arguments from the JUnit 5 context via reflection. + */ + static List getNamedArgs( ExtensionContext context ) { + List namedArgs = new ArrayList<>(); + + if( context.getTestMethod().get().getParameterCount() > 0 ) { + try { + if( context.getClass().getCanonicalName().equals( METHOD_EXTENSION_CONTEXT ) ) { + Field field = context.getClass().getSuperclass().getDeclaredField( "testDescriptor" ); + Object testDescriptor = ReflectionUtil.getFieldValueOrNull( field, context, ERROR ); + if( testDescriptor != null + && testDescriptor.getClass().getCanonicalName().equals( TEST_TEMPLATE_INVOCATION_TEST_DESCRIPTOR ) ) { + Object invocationContext = ReflectionUtil.getFieldValueOrNull( "invocationContext", testDescriptor, ERROR ); + if( invocationContext != null + && invocationContext.getClass().getCanonicalName().equals( PARAMETERIZED_TEST_INVOCATION_CONTEXT ) ) { + Object arguments = ReflectionUtil.getFieldValueOrNull( "arguments", invocationContext, ERROR ); + List args = Arrays.asList( (Object[]) arguments ); + namedArgs = ParameterNameUtil.mapArgumentsWithParameterNames( context.getTestMethod().get(), args ); + } + } + } + } catch( Exception e ) { + log.warn( ERROR, e ); + } + } + + return namedArgs; + } +} 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 034e6f980c..ae518eac20 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,9 +3,7 @@ import static com.tngtech.jgiven.report.model.ExecutionStatus.FAILED; import static com.tngtech.jgiven.report.model.ExecutionStatus.SUCCESS; -import java.util.ArrayList; import java.util.EnumSet; -import java.util.List; import org.junit.jupiter.api.Assumptions; import org.junit.jupiter.api.Tag; @@ -17,7 +15,6 @@ 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.NamedArgument; import com.tngtech.jgiven.report.model.ReportModel; /** @@ -70,9 +67,8 @@ public void afterAll( ExtensionContext context ) throws Exception { @Override public void beforeEach( ExtensionContext context ) throws Exception { - List args = new ArrayList(); - getScenario().startScenario( context.getTestClass().get(), context.getTestMethod().get(), args ); - + getScenario().startScenario( context.getTestClass().get(), context.getTestMethod().get(), + ArgumentReflectionUtil.getNamedArgs(context) ); } @Override @@ -119,4 +115,5 @@ public void postProcessTestInstance( Object testInstance, ExtensionContext conte scenario.getExecutor().injectStages( testInstance ); scenario.getExecutor().readScenarioState( testInstance ); } + } diff --git a/jgiven-junit5/src/test/java/com/tngtech/jgiven/junit5/test/ParameterizedTestTest.java b/jgiven-junit5/src/test/java/com/tngtech/jgiven/junit5/test/ParameterizedTestTest.java new file mode 100644 index 0000000000..9ac1d7ec0e --- /dev/null +++ b/jgiven-junit5/src/test/java/com/tngtech/jgiven/junit5/test/ParameterizedTestTest.java @@ -0,0 +1,27 @@ +package com.tngtech.jgiven.junit5.test; + +import static org.assertj.core.api.Java6Assertions.assertThat; + +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +import com.tngtech.jgiven.annotation.CaseAs; +import com.tngtech.jgiven.junit5.JGivenExtension; +import com.tngtech.jgiven.junit5.ScenarioTest; + +@ExtendWith( JGivenExtension.class ) +public class ParameterizedTestTest extends ScenarioTest { + + @ParameterizedTest( name = "{index} [{arguments}] param name" ) + @ValueSource( strings = { "Hello", "World" } ) + @CaseAs( "Case $1" ) + public void parameterized_scenario( String param ) { + given().some_state(); + when().some_action_with_a_parameter( param ); + then().some_outcome(); + + assertThat( getScenario().getScenarioCaseModel().getDescription() ).isIn( "Case Hello", "Case World" ); + } + +} diff --git a/jgiven-junit5/src/test/java/com/tngtech/jgiven/junit5/test/WhenStage.java b/jgiven-junit5/src/test/java/com/tngtech/jgiven/junit5/test/WhenStage.java index 0ac48bb5b5..763cabc540 100644 --- a/jgiven-junit5/src/test/java/com/tngtech/jgiven/junit5/test/WhenStage.java +++ b/jgiven-junit5/src/test/java/com/tngtech/jgiven/junit5/test/WhenStage.java @@ -17,6 +17,10 @@ void some_action() { someResult = "Some Result"; } + void some_action_with_a_parameter(String s) { + someResult = "s"; + } + public WhenStage some_failing_step() { Assertions.assertTrue(false, "Intentionally failing"); return this;