5.15.1. User and Extension Code

    The following diagram illustrates the relative order of user-supplied code and extension code. User-supplied test and lifecycle methods are shown in orange, with callback code implemented by extensions shown in blue. The grey box denotes the execution of a single test method and will be repeated for every test method in the test class.

    User code and extension code

    The following table further explains the sixteen steps in the User code and extension code diagram.

    In the simplest case only the actual test method will be executed (step 8); all other steps are optional depending on the presence of user code or extension support for the corresponding lifecycle callback. For further details on the various lifecycle callbacks please consult the respective Javadoc for each annotation and extension.

    5.15.2. Wrapping Behavior of Callbacks

    JUnit Jupiter always guarantees wrapping behavior for multiple registered extensions that implement lifecycle callbacks such as BeforeAllCallback, AfterAllCallback, BeforeEachCallback, AfterEachCallback, BeforeTestExecutionCallback, and AfterTestExecutionCallback.

    That means that, given two extensions Extension1 and Extension2 with Extension1 registered before Extension2, any “before” callbacks implemented by Extension1 are guaranteed to execute before any “before” callbacks implemented by Extension2. Similarly, given the two same two extensions registered in the same order, any “after” callbacks implemented by Extension1 are guaranteed to execute after any “after” callbacks implemented by Extension2. Extension1 is therefore said to wrap Extension2.

    JUnit Jupiter also guarantees wrapping behavior within class and interface hierarchies for user-supplied lifecycle methods (see Test Classes and Methods).

    • @BeforeAll methods are inherited from superclasses as long as they are not hidden or overridden. Furthermore, @BeforeAll methods from superclasses will be executed before @BeforeAll methods in subclasses.

      • Similarly, @BeforeAll methods declared in an interface are inherited as long as they are not hidden or overridden, and @BeforeAll methods from an interface will be executed before @BeforeAll methods in the class that implements the interface.
    • @AfterAll methods are inherited from superclasses as long as they are not hidden or overridden. Furthermore, @AfterAll methods from superclasses will be executed after @AfterAll methods in subclasses.

      • Similarly, @AfterAll methods declared in an interface are inherited as long as they are not hidden or overridden, and @AfterAll methods from an interface will be executed after @AfterAll methods in the class that implements the interface.
    • @BeforeEach methods are inherited from superclasses as long as they are not overridden. Furthermore, @BeforeEach methods from superclasses will be executed before @BeforeEach methods in subclasses.

      • Similarly, @BeforeEach methods declared as interface default methods are inherited as long as they are not overridden, and @BeforeEach default methods will be executed before @BeforeEach methods in the class that implements the interface.
    • @AfterEach methods are inherited from superclasses as long as they are not overridden. Furthermore, @AfterEach methods from superclasses will be executed after @AfterEach methods in subclasses.

      • Similarly, @AfterEach methods declared as interface default methods are inherited as long as they are not overridden, and @AfterEach default methods will be executed after methods in the class that implements the interface.

    The following examples demonstrate this behavior. Please note that the examples do not actually do anything realistic. Instead, they mimic common scenarios for testing interactions with the database. All methods imported statically from the Logger class log contextual information in order to help us better understand the execution order of user-supplied callback methods and callback methods in extensions.

    Extension1

    Extension2

    1. import static example.callbacks.Logger.afterEachCallback;
    2. import static example.callbacks.Logger.beforeEachCallback;
    3. import org.junit.jupiter.api.extension.AfterEachCallback;
    4. import org.junit.jupiter.api.extension.BeforeEachCallback;
    5. import org.junit.jupiter.api.extension.ExtensionContext;
    6. public class Extension2 implements BeforeEachCallback, AfterEachCallback {
    7. @Override
    8. public void beforeEach(ExtensionContext context) {
    9. beforeEachCallback(this);
    10. }
    11. @Override
    12. public void afterEach(ExtensionContext context) {
    13. afterEachCallback(this);
    14. }
    15. }

    AbstractDatabaseTests

    DatabaseTestsDemo

    1. import static example.callbacks.Logger.afterEachMethod;
    2. import static example.callbacks.Logger.beforeAllMethod;
    3. import static example.callbacks.Logger.beforeEachMethod;
    4. import static example.callbacks.Logger.testMethod;
    5. import org.junit.jupiter.api.AfterAll;
    6. import org.junit.jupiter.api.AfterEach;
    7. import org.junit.jupiter.api.BeforeAll;
    8. import org.junit.jupiter.api.BeforeEach;
    9. import org.junit.jupiter.api.Test;
    10. /**
    11. * Extension of {@link AbstractDatabaseTests} that inserts test data
    12. * into the database (after the database connection has been opened)
    13. * and deletes test data (before the database connection is closed).
    14. */
    15. @ExtendWith({ Extension1.class, Extension2.class })
    16. class DatabaseTestsDemo extends AbstractDatabaseTests {
    17. @BeforeAll
    18. static void beforeAll() {
    19. beforeAllMethod(DatabaseTestsDemo.class.getSimpleName() + ".beforeAll()");
    20. }
    21. @BeforeEach
    22. void insertTestDataIntoDatabase() {
    23. beforeEachMethod(getClass().getSimpleName() + ".insertTestDataIntoDatabase()");
    24. }
    25. @Test
    26. void testDatabaseFunctionality() {
    27. testMethod(getClass().getSimpleName() + ".testDatabaseFunctionality()");
    28. }
    29. @AfterEach
    30. void deleteTestDataFromDatabase() {
    31. afterEachMethod(getClass().getSimpleName() + ".deleteTestDataFromDatabase()");
    32. @AfterAll
    33. static void afterAll() {
    34. beforeAllMethod(DatabaseTestsDemo.class.getSimpleName() + ".afterAll()");
    35. }
    36. }

    When the DatabaseTestsDemo test class is executed, the following is logged.

    The following sequence diagram helps to shed further light on what actually goes on within the JupiterTestEngine when the DatabaseTestsDemo test class is executed.

    extensions DatabaseTestsDemo

    DatabaseTestsDemo

    JUnit Jupiter does not guarantee the execution order of multiple lifecycle methods that are declared within a single test class or test interface. It may at times appear that JUnit Jupiter invokes such methods in alphabetical order. However, that is not precisely true. The ordering is analogous to the ordering for @Test methods within a single test class.

    In addition, JUnit Jupiter does not support wrapping behavior for multiple lifecycle methods declared within a single test class or test interface.

    The following example demonstrates this behavior. Specifically, the lifecycle method configuration is broken due to the order in which the locally declared lifecycle methods are executed.

    • Test data is inserted before the database connection has been opened, which results in a failure to connect to the database.

    • The database connection is closed before deleting the test data, which results in a failure to connect to the database.

    BrokenLifecycleMethodConfigDemo

    1. import static example.callbacks.Logger.afterEachMethod;
    2. import static example.callbacks.Logger.beforeEachMethod;
    3. import static example.callbacks.Logger.testMethod;
    4. import org.junit.jupiter.api.AfterEach;
    5. import org.junit.jupiter.api.BeforeEach;
    6. import org.junit.jupiter.api.Test;
    7. import org.junit.jupiter.api.extension.ExtendWith;
    8. /**
    9. * Example of "broken" lifecycle method configuration.
    10. *
    11. * <p>Test data is inserted before the database connection has been opened.
    12. *
    13. * <p>Database connection is closed before deleting test data.
    14. */
    15. @ExtendWith({ Extension1.class, Extension2.class })
    16. class BrokenLifecycleMethodConfigDemo {
    17. @BeforeEach
    18. void connectToDatabase() {
    19. beforeEachMethod(getClass().getSimpleName() + ".connectToDatabase()");
    20. }
    21. @BeforeEach
    22. void insertTestDataIntoDatabase() {
    23. beforeEachMethod(getClass().getSimpleName() + ".insertTestDataIntoDatabase()");
    24. }
    25. @Test
    26. void testDatabaseFunctionality() {
    27. testMethod(getClass().getSimpleName() + ".testDatabaseFunctionality()");
    28. }
    29. @AfterEach
    30. void deleteTestDataFromDatabase() {
    31. afterEachMethod(getClass().getSimpleName() + ".deleteTestDataFromDatabase()");
    32. }
    33. @AfterEach
    34. void disconnectFromDatabase() {
    35. afterEachMethod(getClass().getSimpleName() + ".disconnectFromDatabase()");
    36. }

    When the BrokenLifecycleMethodConfigDemo test class is executed, the following is logged.

    The following sequence diagram helps to shed further light on what actually goes on within the JupiterTestEngine when the BrokenLifecycleMethodConfigDemo test class is executed.