Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Parameterize both class and tests in JUnit 5

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?

like image 451
Sergei Tachenov Avatar asked Apr 21 '18 06:04

Sergei Tachenov


People also ask

What is parameterized test in JUnit 5?

Some times we may need to run same tests with different arguments or values, Junit 5 Jupiter Parameterized tests makes it possible to run a test multiple times with different arguments. They are declared just like regular @Test methods but use the @ParameterizedTest annotation instead of @Test annotation.

How does JUnit parameterize a class?

Parameterized tests allow a developer to run the same test over and over again using different values. There are five steps that you need to follow to create a parameterized test. Annotate test class with @RunWith(Parameterized. class).

Does JUnit support parameterized tests where tests can be executed multiple times with different parameter values?

Overview. JUnit 5, the next generation of JUnit, facilitates writing developer tests with shiny new features. One such feature is parameterized tests. This feature enables us to execute a single test method multiple times with different parameters.


1 Answers

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 @CartesianProductTest. Using the extended the example from above:

class CartProdTest {

    @CartesianProductTest(factory = "classWithObjectParams")
    void testClassWithObject(String classParam, Object objectParam) {
        System.out.println(classParam + " " + objectParam);
    }

    static CartesianProductTest.Sets classWithObjectParams() {
        return new CartesianProductTest.Sets()
                .addAll(classParams())
                .add(new Object());
    }

    @CartesianProductTest(factory = "classWithIntegerParams")
    void testClassWithInteger(String classParam, int integerParam) {
        System.out.println(classParam + " " + integerParam);
    }

    static CartesianProductTest.Sets classWithIntegerParams() {
        return new CartesianProductTest.Sets()
                .addAll(classParams())
                .add(1, 2, 3, 4, 5, 6);
    }

    @CartesianProductTest(factory = "classWithBooleanParams")
    void testClassWithBoolean(String classParam, boolean booleanParam) {
        System.out.println(classParam + " " + booleanParam);
    }

    static CartesianProductTest.Sets classWithBooleanParams() {
        return new CartesianProductTest.Sets()
                .addAll(classParams())
                .add(false, true);
    }

    static Stream<String> classParams() {
        return Stream.of("classParam1", "classParam2", "classParam3");
    }

}

This produces the same output.

like image 180
beatngu13 Avatar answered Sep 27 '22 19:09

beatngu13