Contents

Junit 5 | Creating Dynamic tests with @TestFactory

Introduction

JUnit 4 introduced parametrized tests as a way to create test cases during runtime. To use this feature you would also have to include the junit-jupiter-params dependency. JUnit 5 introduced the @TestFactory to let you dynamically create test cases.

Methods annotated with @TestFactory differ from @Test in that they are not test-cases of themselves but return one or more test cases. The generated test cases automatically appear inside your IDE as they would with parameterized tests.

Creating Dynamic tests with @TestFactory

There are many ways to create dynamic tests. See the JUnit documentation here for more examples. The example below shows us how to create a Stream of dynamic tests for JUnit to run. The example below exists of three parts:

  • InputStream: The input for the dynamic test cases.
  • DisplayName: The name of the test case you will see in the output console of your IDE.
  • TestExecutor: A lambda expression with your test code.

Passing these three objects to the DynamicTest.stream() will create a stream of dynamic tests, one for each object in the input stream.

1
2
3
4
5
6
7
8
9
    @TestFactory
    Stream<DynamicTest> generateTestCases(){

        Stream<Integer> inputStream = IntStream.range(0, 10).boxed();
        Function<Integer, String> displayName = input -> "Test input: " + input + " should be smaller than 10";
        ThrowingConsumer<Integer> testExecutor = input -> assertTrue(input < 10);

        return DynamicTest.stream(inputStream, displayName, testExecutor);
    }

What it looks like in an IDE

The console output will look like the result below. Each dynamic test will result in one line in the console.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
TestingTestFactories
    generateTestCases()
        Test input: 0 should be smaller than 10
        Test input: 1 should be smaller than 10
        Test input: 2 should be smaller than 10
        Test input: 3 should be smaller than 10
        Test input: 4 should be smaller than 10
        Test input: 5 should be smaller than 10
        Test input: 6 should be smaller than 10
        Test input: 7 should be smaller than 10
        Test input: 8 should be smaller than 10
        Test input: 9 should be smaller than 10

Life cycle callbacks

An important detail of the @TestFactory is that the lifecycle of the dynamic tests it creates is different from @Test and @ParameterizedTest cases. Lifecycle callbacks like @BeforeEach and @AfterEach are called before and after the @TestFactory annotated method. Lifecycle methods are not called before or after a dynamic test is created or run.

Conclusion

With everything taken into consideration, both ParametrizedTest and @TestFactory will create tests cases dynamically either with a stream of input parameters as is done with ParametrizedTests or with a stream of dynamic test cases from a @TestFactory annotated method. It mainly comes down to which of the two methods best fits the problem you are trying to solve and the style in which you want to do it.

References and Further reading

For the documentation and more examples of how to create a dynamic test, please look at the documentation https://junit.org/junit5/docs/current/user-guide/#writing-tests-dynamic-tests.

More about testing in Java:

If you want to read more about Java when I write about it – you can follow me on Twitter.