70

Is it possible to use JUnit 5's parameterized new features to run test classes to receive test parameters instead of doing it at method level?

With JUnit 4, a runner such as @RunWith(Parameterized::class) plus inheritance could be used to pass an array of parameters to subclasses, but I am not sure if it is possible to achieve something equivalent by using the new JUnit 5 API.

4
  • 7
    Sounds like you may be looking for github.com/junit-team/junit5/issues/878 Commented Sep 12, 2017 at 18:25
  • @mkobit Exactly. Are there any alternatives for that? Commented Sep 12, 2017 at 19:07
  • There may be a workaround, it would be useful to sample code about what you would like to accomplish. Commented Sep 13, 2017 at 15:08
  • Similar popular question: stackoverflow.com/questions/46897134/… Commented Feb 2, 2020 at 19:33

6 Answers 6

25

Short answer
there's no way to parametrize class creation with JUnit 5 following the style of JUnit 4.

Fortunately, the very intention of separation test logic and test input data (parameters) can be implemented differently.


JUnit 5 has its own approach for making parameterized tests, and, of course, it is different from JUnit 4. The new approach does not allow to use parameterized fixtures at the class level, i.e. through its every test method. So every parameterized test method should be explicitly annotated with a link to parameters.

JUnit 5 provides a plenty of parameter source types, that could be found in documentation and guides

In your case, the simplest way to migrate from @Parameters of Junit 4 is using @MethodSource or @ArgumentsSource of org.junit.jupiter.params.provider.*.

JUnit 4:

@RunWith(Parameterized.class)
public class MyTestWithJunit4 {
    @Parameters
    public static Collection<Object[]> data() {
      return Arrays.asList(new Object[][] {     
               { 0, 0, 0 },
               { 1, 2, 3 }, 
               { 5, 3, 8 } 
      });
    }

    int first;
    int second;
    int sum;

    public MyTestWithJunit4(int first, int second, int sum) {
      this.first = first;
      this.second = second;
      this.sum = sum;
    }

    @Test
    public void test() {
      assertEquals(sum, first + second));
    }
}

JUnit 5 (with @MethodSource):

class MyTestWithJunit5 {

  @DisplayName("Test with @MethodSource")
  @ParameterizedTest(name = "{index}: ({0} + {1}) => {2})")
  @MethodSource("localParameters")
  void test(int first, int second, int sum) {
    assertEquals(sum, first + second);
  }

  static Stream<Arguments> localParameters() {
    return Stream.of(
        Arguments.of(0, 0, 0),
        Arguments.of(1, 2, 3),
        Arguments.of(5, 3, 8)
    );
  }
}

JUnit 5 (with @ArgumentsSource):

class MyTestWithJunit5 {
  @DisplayName("Test with @ArgumentsSource")
  @ParameterizedTest(name = "{index}: ({0} + {1}) => {2})")
  @ArgumentsSource(Params.class)
  void test(int first, int second, int sum) {
    assertEquals(sum, first + second);
  }

  static class Params implements ArgumentsProvider {
    @Override
    public Stream<? extends Arguments> provideArguments(ExtensionContext context) {
      return Stream.of(
          Arguments.of(0, 0, 0),
          Arguments.of(1, 2, 3),
          Arguments.of(5, 3, 8)
      );
    }
  }
}

Consider that a method in @MethodSource and a class in @ArgumentsSource could be described anywhere, not only inside the same class where the test methods are located. Also @MethodSource allows to provide multiple source methods, since its value is a String[].

Some remarks and comparison

In JUnit 4 we could only have a single factory method providing parameters, and the tests were supposed to be built around those parameters. On the contrary, JUnit 5 gives more abstraction and flexibility in binding parameters and decouples test logic from its parameters, which are secondary. That allows building tests independently from parameter sources, and easily change them when needed.

Dependency requirement

Parameterized tests feature is not included in the core junit-jupiter-engine, but is located in a separate dependency junit-jupiter-params.

Sign up to request clarification or add additional context in comments.

5 Comments

Seems to be a serious lack in Junit 5. We have Nested now to test entire flows with short to the point test methods, but no way to initialize those flows with different parameters.
This is an awesome summary of what can be done but this doesn't stress enough the sad fact (and the actual answer to the question) that there's no way to parametrize class creation in Junit 5 (and was possible in Junit 4.
7 years and still not added. very sad. This was by far the nicest way to test multiple implementations of an interface (A very common LiskovSP concept). Feels like it's never happending. Check here: github.com/junit-team/junit5/issues/878
JUnit 5.13 finally added @ParameterizedClass annotation.
Junit 5.13.4 brings new ParameterizedClass and ClassTemplate With this its now possible.
11

Create a meta annotation that specifies the parameterisation, and apply it to the test methods:

public class MyTest {
    @ParameterizedTest(name = "{0}")
    @MethodSource("allImplementations")
    @Retention(RetentionPolicy.RUNTIME)
    private @interface TestAllImplementations {
    }

    static Stream<Arguments> allImplementations() {
        return Stream.of(
             Arguments.of("Serial", new SerialWidget()),
             Arguments.of("Concurrent", new ConcurrentWidget())
        );
    }

    @TestAllImplementations
    void myFirstTest(String name, Widget implementation) {
        /* ... */
    }

    @TestAllImplementations
    void mySecondTest(String name, Widget implementation) {
        /* ... */
    }
}

1 Comment

This is the way.
10
+500

The best solution I could find was to make the old test abstract. Then create a new class that has nested tests for each variation eg.

public class NewParametricTest {
  @Nested
  public class NewParametricTestVariation1Test
    extends AbstractOriginalToBeParameticizedTest {
    public NewParametricTestVariation1Test() {
      super("Some parameter", 1);
    }
  }

  // add others here
}

1 Comment

Amazing! This saved my day! Thank you! :)
5

JUnit 5.13.0 introduced a ParameterizedClass annotation that works similarly to the ParameterizedTest annotation, but on a class level, e.g.:

@ParameterizedClass
@ValueSource(ints = {1, 12, 123})
public class MyParameterizedTestClass {
    @Parameter
    private int x;

    @Test
    void testX() {
        assertTrue(x < 999);
    }
}

Comments

1

I would rather implement a JUnit 5 specific style by means of either dynamic tests, or test interfaces, than to contort the framework in supporting exactly what you need.

I (personally) find the aforementioned more flexible than trying to force the framework to do this, which will end up producing (in the long run) more boilerplate code.

Most developers tend to lean on @MethodSource, but if not used correctly, this produces pretty awful tests, as people tend to encode the expected results as part of the stream of parameters.

While test interfaces don't really solve this exactly in the way you would like, It provides (imo) a more natural OO approach to solving this issue.

For example:

interface MyJunit5StyleTest {

    String getFoo();

    int getBar();

    @Test
    default void testUsingFooBar() {
        assertThat(getFoo()).length().isGreaterThan(0);
        assertThat(getBar()).isGreaterThan(2);
    }
}

Then you can have implementations which will by default invoke all tests defined in the interface, but can also define their own tests:

class MyFirstTest implements MyJunit5StyleTest {

    @Override
    String getFoo() {
        return "my_first_test_foo";
    }


    @Override
    int getBar() {
        return -1;
    }

    @Test
    void specificFirstTest() {
        // do something else here
    }
}

class MySecondTest implements MyJunit5StyleTest {

    @Override
    String getFoo() {
        return "my_second_test_foo";
    }


    @Override
    int getBar() {
        return 42;
    }
}

1 Comment

When I first implemented @AmanicA's idea, I used the template method pattern, but I think it looked better with the constructor. I guess template methods may work better if you had a variety of subclasses, each changing a small subset of defaults.
0
@ParameterizedTest()
@MethodSource("dataProvider")
void testWithDataProvider(String str, List<JSONObject> list) {
    System.out.println();

}

static Stream<Arguments> dataProvider() {
    String json = """
            {
                "test":1 
            }
                    """;
    return Stream.of(
            arguments("abc", Arrays.asList(new JSONObject(json))),
            arguments("lemon", Arrays.asList(new JSONObject(json)))
    );

I bypassed this complexity in this way

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.