Search code examples
javajunit5

Using RegisterExtension to manage external resource in JUnit 5 Suite class


I am looking to migrate a JUnit 4 test suite to JUnit 5. The JUnit 4 test suite currently looks something like this:

@RunWith(Suite.class)
@SuiteClasses({FirstTest.class, SecondTest.class})
public class JUnit4Suite {
  @ClassRule
  public static JUnit4Server MY_SERVER = new JUnit4Server();
}

where MY_SERVER is an ExternalResource that all the tests in the suite use, to, say publish something (JUnit4Suite.MY_SERVER.publish(...)):

public class JUnit4Server extends ExternalResource {
  @Override
  protected final void before() throws Throwable {
    // start the server
  }

  @Override
  protected final void after() {
    // stop the server
  }
}

The server needs to be initialized only once, at the start of the suite run, before any test runs, and stopped once all tests have finished executing. This currently works fine.

Using JUnit 5, I am coming up with something like this:

@Suite
@SelectClasses({FirstTest.class, SecondTest.class})
public class JUnit5Suite {
  @RegisterExtension
  public static JUnit5Server MY_SERVER = new JUnit5Server();
}

where MY_SERVER now looks like this:

public class JUnit5Server implements BeforeAllCallback, AfterAllCallback {
  @Override
  public void beforeAll(ExtensionContext context) throws Exception {
    ...
  }
}

However, when I run the JUnit5Suite, the server instance gets created fine, however the beforeAll method in the server does not get executed. Is there something missing?


Solution

  • According to javadoc

    @BeforeAll is used to signal that the annotated method should be executed before all tests in the current test class. In contrast to @BeforeEach methods, @BeforeAll methods are only executed once for a given test class.

    Because a test suite probably contains multiple test classes, it's reasonable to change callbacks' behaviour from test class level to test suite level.

    Fortunately, JUnit Platform Suite API 1.11 introduced @BeforeSuite and @AfterSuite annotations.

    @BeforeSuite is used to signal that the annotated method should be executed before all tests in the current test suite.

    and

    @AfterSuite is used to signal that the annotated method should be executed after all tests in the current test suite.

    With this soulion the server will start before (and will stop after) all tests in the current test suite.

    @Suite
    @SelectClasses({FirstTest.class, SecondTest.class})
    public class SampleSuite {
        static SampleServer server = new SampleServer();
    
        @BeforeSuite
        static void beforeSuite() {
            System.out.println("Before Suite");
            server.start();
        }
    
        @AfterSuite
        static void afterSuite() {
            server.shutdown();
            System.out.println("After Suite");
        }
    }
    
    public class SampleServer {
        public void start() {
            System.out.println("Starting SampleServer");
        }
    
        public void shutdown() {
            System.out.println("SampleServer shutdown");
        }
    }
    

    After running mvn clean test command the output should be like this:

    [INFO] -------------------------------------------------------
    [INFO]  T E S T S
    [INFO] -------------------------------------------------------
    [INFO] Running io.github.zforgo.jupiter.SampleSuite
    Before Suite
    Starting SampleServer
    [INFO] Running io.github.zforgo.jupiter.FirstTest
    FirstTest.bar
    FirstTest.foo
    [INFO] Tests run: 2, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.050 s -- in io.github.zforgo.jupiter.FirstTest
    [INFO] Running io.github.zforgo.jupiter.SecondTest
    SecondTest.bar
    SecondTest.foo
    [INFO] Tests run: 2, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.007 s -- in io.github.zforgo.jupiter.SecondTest
    SampleServer shutdown
    After Suite
    [INFO] Tests run: 0, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.122 s -- in io.github.zforgo.jupiter.SampleSuite