Writing test methods with shared expensive set-up

42 Views Asked by At

Imagine this scenario. I need to write a bunch of test methods that all require the same slow and complex set-up. I need to ensure that:

  1. Every test method tests only one thing. If I have a myriad of unrelated assertions in one test method, and the test fails, it won't be immediately clear what exactly went wrong (and the test method will be huge, compromising readability and maintainability)
  2. Given N is the number of tests that share the set-up, the set-up is not performed N times (because it's slow and tests should be fast). Simply extracting the set-up code or including a @BeforeEach method is not an option.
  3. Not all test methods are set up the same way. Some may require a fast and simple set-up, others may require a different slow and complex set-up. However, all test methods test the behavior of one class so splitting it into multiple test classes doesn't feel right
public class SomeTest {
    @Test
    public void testOneThing() {
        // slow and complex set-up
        // assert(s)
    }

    @Test
    public void testAnotherThing() {
        // slow and complex set-up
        // other assert(s)
    }

    // more tests with the shared set-up
}

A chat bot suggested this interesting idea:

public class SomeTest {
    @BeforeEach
    public void expensiveSetup() {
        // Perform slow and complex setup here
    }

    @ParameterizedTest
    @MethodSource("testMethods")
    public void runTest(Runnable testMethod) {
        testMethod.run();
    }

    // Define the test methods as static factory methods returning Runnable
    private static Stream<Runnable> testMethods() {
        return Stream.of(
                SomeTest::testOneThing,
                SomeTest::testAnotherThing
                // Add more test methods here
        );
    }

    // Individual test methods with assertions
    private static void testOneThing() {
        // Perform assertions for one thing
    }

    private static void testAnotherThing() {
        // Perform assertions for another thing
    }
}

However, the drawback is that I wouldn't immediately know which assertion runnable failed if something goes wrong, only its index. Replacing lambdas with anonymous classes that override toString() would be awkward

What can I do in Junit 5 to meet my criteria?

0

There are 0 best solutions below