Is there a way to parameterize both test class (like you could do with Parameterized
and @Parameters
in JUnit 4) and test methods (like you could do with JUnitParams in JUnit 4 or with @ParameterizedTest
in JUnit 5)? I need to get the Cartesian product of the parameters in the end.
Example of a partial test for java.nio.ByteBuffer
using the desired approach:
public class ByteBufferTest { private static final int BUFFER_SIZE = 16384; private final ByteOrder byteOrder; private ByteBuffer sut; @Factory(dataProvider = "byteOrders") public ByteBufferTest(ByteOrder byteOrder) { this.byteOrder = byteOrder; } @DataProvider public static Object[][] byteOrders() { return new Object[][] { {ByteOrder.BIG_ENDIAN}, {ByteOrder.LITTLE_ENDIAN} }; } @BeforeMethod public void setUp() { sut = ByteBuffer.allocate(BUFFER_SIZE); sut.order(byteOrder); } @Test(dataProvider = "validPositions") public void position(int position) { System.out.println(byteOrder + " position " + position); sut.position(position); assertThat(sut.position()).isEqualTo(position); } @DataProvider public static Object[][] validPositions() { return new Object[][] {{0}, {1}, {BUFFER_SIZE - 1}}; } @Test(dataProvider = "intPositionsAndValues") public void putInt(int position, int value, byte[] expected) { System.out.println(byteOrder + " position " + position + " value " + value); sut.putInt(position, value); assertThat(sut.array()) .contains(expected[0], atIndex(position)) .contains(expected[1], atIndex(position + 1)) .contains(expected[2], atIndex(position + 2)) .contains(expected[3], atIndex(position + 3)); } @DataProvider public Object[][] intPositionsAndValues() { if (byteOrder == ByteOrder.BIG_ENDIAN) { return new Object[][]{ {0, 0, new byte[4]}, {5, 123456789, new byte[] {0x07, 0x5B, (byte) 0xCD, 0x15}}, }; } else { return new Object[][]{ {0, 0, new byte[4]}, {5, 123456789, new byte[] {0x15, (byte) 0xCD, 0x5B, 0x07}}, }; } } }
It produces:
LITTLE_ENDIAN position 0 LITTLE_ENDIAN position 1 LITTLE_ENDIAN position 16383 BIG_ENDIAN position 0 BIG_ENDIAN position 1 BIG_ENDIAN position 16383 LITTLE_ENDIAN position 0 value 0 LITTLE_ENDIAN position 5 value 123456789 BIG_ENDIAN position 0 value 0 BIG_ENDIAN position 5 value 123456789
We’re thinking about migrating to JUnit 5 from TestNG, but we use this kind of thing pretty often. The use of the byte order as a class-level parameter in the example above is not a coincidence: we often need tests for various binary data processor, where the test constructor would take a byte/bit order argument, and we run every test for both Big Endian and Little Endian.
I was thinking about creating an extension for this and then use ExtendWith
, but maybe there is an existing extension or something that works out-of-the-box that I have missed?
Advertisement
Answer
JUnit Jupiter (Vanilla)
You can combine multiple sources within e.g. a @MethodSource
. Based on your TestNG example:
class ExampleTest { @ParameterizedTest @MethodSource("args") void test(String classParameter, String testParameter) { System.out.println(classParameter + " " + testParameter); } static Stream<Arguments> args() { return classParameters().flatMap( classParameter -> testParameters().map( testParameter -> Arguments.of(classParameter, testParameter))); } static Stream<String> classParameters() { return Stream.of("classParam1", "classParam2"); } static Stream<String> testParameters() { return Stream.of("testParam1", "testParam2"); } }
This produces:
classParam1 testParam1 classParam1 testParam2 classParam2 testParam1 classParam2 testParam2
As requested by the OP, here is “an example with at least two test methods with different set of parameters”:
class ExampleTest { static Stream<String> classParams() { return Stream.of("classParam1", "classParam2", "classParam3"); } static Stream<Arguments> withClassParams(List<?> methodParams) { return classParams().flatMap( classParam -> methodParams.stream().map( methodParam -> Arguments.of(classParam, methodParam))); } @ParameterizedTest @MethodSource void booleanParams(String classParam, boolean booleanParam) { System.out.println(classParam + " " + booleanParam); } static Stream<Arguments> booleanParams() { return withClassParams(List.of(false, true)); } @ParameterizedTest @MethodSource void integerParams(String classParam, int integerParam) { System.out.println(classParam + " " + integerParam); } static Stream<Arguments> integerParams() { return withClassParams(List.of(1, 2, 3, 4, 5, 6)); } @ParameterizedTest @MethodSource void objectParams(String classParam, Object objectParam) { System.out.println(classParam + " " + objectParam); } static Stream<Arguments> objectParams() { return withClassParams(List.of(new Object())); } }
3 class parameters plus 3 different method parameters with different types and sizes, producing the following output:
classParam1 java.lang.Object@35cabb2a classParam2 java.lang.Object@35cabb2a classParam3 java.lang.Object@35cabb2a classParam1 1 classParam1 2 classParam1 3 classParam1 4 classParam1 5 classParam1 6 classParam2 1 classParam2 2 classParam2 3 classParam2 4 classParam2 5 classParam2 6 classParam3 1 classParam3 2 classParam3 3 classParam3 4 classParam3 5 classParam3 6 classParam1 false classParam1 true classParam2 false classParam2 true classParam3 false classParam3 true
JUnit Pioneer
There is the JUnit Pioneer extension pack for JUnit Jupiter. It comes with @CartesianTest
. Using the extended the example from above:
class CartProdTest { @CartesianTest @CartesianTest.MethodFactory("classWithObjectParams") void testClassWithObject(String clazz, Object object) { System.out.println(clazz + " " + object); } static ArgumentSets classWithObjectParams() { return ArgumentSets .argumentsForFirstParameter(classParams()) .argumentsForNextParameter(new Object()); } @CartesianTest @CartesianTest.MethodFactory("classWithIntegerParams") void testClassWithInteger(String clazz, int integerParam) { System.out.println(clazz + " " + integerParam); } static ArgumentSets classWithIntegerParams() { return ArgumentSets .argumentsForFirstParameter(classParams()) .argumentsForNextParameter(1, 2, 3, 4, 5, 6); } @CartesianTest @CartesianTest.MethodFactory("classWithBooleanParams") void testClassWithBoolean(String clazz, boolean booleanParam) { System.out.println(clazz + " " + booleanParam); } static ArgumentSets classWithBooleanParams() { return ArgumentSets .argumentsForFirstParameter(classParams()) .argumentsForNextParameter(false, true); } static String[] classParams() { return new String[]{"classParam1", "classParam2", "classParam3"}; } }
This produces the same output.