Labs ICT
โญ Pro Login

@BeforeEach, @AfterEach, @BeforeAll, @AfterAll

4 min read | JUnit Testing
โญ

Want the full learning experience?

Get structured courses, certificates, projects, and instructor support with LabsICT Pro.

Explore Pro Courses

One of the most common patterns in testing is setting up resources before tests and cleaning them up afterward. JUnit 5 gives you four lifecycle annotations to handle exactly this. Let us see how they work and when to use each one.

The Four Lifecycle Annotations


+---------------+---------------------+----------------------------------+
|  Annotation   |  Scope              |  When it runs                    |
+---------------+---------------------+----------------------------------+
| @BeforeAll    |  Once per class     |  Before ANY test method          |
| @BeforeEach   |  Once per test      |  Before EACH test method         |
| @AfterEach    |  Once per test      |  After EACH test method          |
| @AfterAll     |  Once per class     |  After ALL test methods          |
+---------------+---------------------+----------------------------------+

Execution order for two tests:

@BeforeAll (once)
  โ”œโ”€โ”€ @BeforeEach
  โ”‚     โ””โ”€โ”€ test1
  โ”œโ”€โ”€ @AfterEach
  โ”œโ”€โ”€ @BeforeEach
  โ”‚     โ””โ”€โ”€ test2
  โ””โ”€โ”€ @AfterEach
@AfterAll (once)
    

@BeforeEach and @AfterEach

These run before and after every test method. Perfect for setting up fresh test data or cleaning up after each test:


class UserServiceTest {

    private UserService userService;
    private User testUser;

    @BeforeEach
    void setUp() {
        userService = new UserService();
        testUser = new User("John", "john@example.com");
    }

    @AfterEach
    void tearDown() {
        userService.clearAll();
        testUser = null;
    }

    @Test
    void shouldAddUser() {
        userService.add(testUser);
        assertTrue(userService.exists("John"));
    }

    @Test
    void shouldRemoveUser() {
        userService.add(testUser);
        userService.remove("John");
        assertFalse(userService.exists("John"));
    }
}
    

Notice how each test starts with a fresh userService and testUser. This ensures tests do not interfere with each other โ€” each test is completely independent.

@BeforeAll and @AfterAll

These run once for the entire test class. Use them for expensive setup operations like starting a database, loading a configuration file, or creating a shared resource:


class DatabaseTest {

    private static Connection connection;

    @BeforeAll
    static void setUpDatabase() {
        // This runs ONCE before all tests
        connection = DriverManager.getConnection(
            "jdbc:h2:mem:testdb;DB_CLOSE_DELAY=-1"
        );
        // Create tables, insert seed data
        connection.createStatement().execute(
            "CREATE TABLE users (id INT, name VARCHAR(50))"
        );
    }

    @AfterAll
    static void closeDatabase() throws SQLException {
        // This runs ONCE after all tests
        connection.close();
    }

    @Test
    void shouldInsertUser() throws SQLException {
        connection.createStatement().execute(
            "INSERT INTO users VALUES (1, 'John')"
        );
        ResultSet rs = connection.createStatement()
            .executeQuery("SELECT * FROM users WHERE id = 1");
        assertTrue(rs.next());
        assertEquals("John", rs.getString("name"));
    }
}
    

Important: @BeforeAll and @AfterAll methods must be static (unless you use @TestInstance(PER_CLASS)).

Real-World Example: Testing a File Service


class FileServiceTest {

    private static final String TEST_DIR = "test-output";
    private FileService fileService;

    @BeforeAll
    static void createTestDirectory() {
        new File(TEST_DIR).mkdirs();
    }

    @AfterAll
    static void deleteTestDirectory() throws IOException {
        Files.walk(Paths.get(TEST_DIR))
            .sorted(Comparator.reverseOrder())
            .map(Path::toFile)
            .forEach(File::delete);
    }

    @BeforeEach
    void setUp() {
        fileService = new FileService(TEST_DIR);
    }

    @AfterEach
    void cleanUpFiles() {
        // Remove any files created during a test
        File dir = new File(TEST_DIR);
        for (File file : dir.listFiles()) {
            if (file.isFile()) file.delete();
        }
    }

    @Test
    void shouldCreateFile() {
        fileService.createFile("test.txt", "hello");
        assertTrue(new File(TEST_DIR, "test.txt").exists());
    }

    @Test
    void shouldWriteContent() throws IOException {
        fileService.createFile("data.txt", "world");
        String content = Files.readString(
            Paths.get(TEST_DIR, "data.txt")
        );
        assertEquals("world", content);
    }
}
    

Common Mistakes

Here are some pitfalls to avoid:

  • Forgetting static - @BeforeAll and @AfterAll methods must be static
  • Overusing @BeforeAll - If setup is different for each test, use @BeforeEach
  • Shared mutable state - Avoid modifying fields in @BeforeAll that tests also modify
  • No cleanup - Always clean up resources in @AfterEach or @AfterAll

// BAD: Shared state leads to flaky tests
class BadTest {
    private List<String> items = new ArrayList<>(); // Shared!

    @Test
    void test1() {
        items.add("a");
        assertEquals(1, items.size()); // Passes
    }

    @Test
    void test2() {
        items.add("b");
        assertEquals(1, items.size()); // FAILS! items has 2 elements
    }
}

// GOOD: Fresh state for each test
class GoodTest {
    private List<String> items;

    @BeforeEach
    void setUp() {
        items = new ArrayList<>(); // Fresh list each time
    }

    @Test
    void test1() {
        items.add("a");
        assertEquals(1, items.size()); // Passes
    }

    @Test
    void test2() {
        items.add("b");
        assertEquals(1, items.size()); // Passes
    }
}
    

๐Ÿงช Quick Quiz

What annotation runs AFTER ALL test methods in a class?