assert_
glossary ↓ downloads
track: java · selenium
suite 05 · test frameworks & structure

Structuring a test class

Test frameworks & structure15 min
as a manual tester

Every test case has preconditions, steps, and cleanup — your templates enforce it.

in automation

JUnit annotations are that template: @BeforeEach is preconditions, @Test is steps+expected, @AfterEach is cleanup — guaranteed to run even on failure.

You've written tests with browser setup and quit() repeated inside each one — and if an assertion fails, quit() never runs, leaking a browser. JUnit's lifecycle annotations fix both, and they're the skeleton of every professional suite:

BaseTest.java — every test class extends this
import org.junit.jupiter.api.*;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.chrome.ChromeDriver;

public class BaseTest {
    protected WebDriver driver;      // protected = subclasses can use it

    @BeforeEach                       // runs before EVERY test method
    void setUp() {
        driver = new ChromeDriver();
    }

    @AfterEach                        // runs after every test — EVEN failed ones
    void tearDown() {
        if (driver != null) driver.quit();
    }
}
LoginTest.java — clean tests, inherited plumbing
public class LoginTest extends BaseTest {   // inheritance, from the OOP lesson

    @Test
    @DisplayName("Valid user lands on Products page")
    void validLogin() {
        LoginPage login = new LoginPage(driver);
        login.open();
        login.loginAs("standard_user", "secret_sauce");
        assertEquals("Products", new ProductsPage(driver).getTitle());
    }

    @Test
    @DisplayName("Locked-out user sees an error")
    void lockedOutLogin() {
        LoginPage login = new LoginPage(driver);
        login.open();
        login.loginAs("locked_out_user", "secret_sauce");
        assertTrue(login.getErrorMessage().contains("locked out"));
    }
}
predict: what does this print? think it through, then reveal
output
[INFO] Running LoginTest
[INFO] Tests run: 2, Failures: 0, Errors: 0, Skipped: 0
[INFO] BUILD SUCCESS

Each test gets a fresh browser (no state leaking between tests — the silent killer of reliability), and teardown is guaranteed. @DisplayName makes reports readable by humans; write them like test case titles.

The assertion toolkit

the assertions you'll actually use
assertEquals(expected, actual);            // the workhorse
assertEquals(expected, actual, "message shown on failure");
assertTrue(condition);   assertFalse(condition);
assertNotNull(object);
assertAll(                                 // check several, report ALL failures
    () -> assertEquals("Products", title),
    () -> assertEquals("1", cartCount)
);
assertEquals order matters: it's (expected, actual). Swap them and failure messages lie to you: "expected <the bug> but was <the truth>". Drill it now.
⚠ when it breaks — the classics
My tests exist, Maven runs zero of them
[INFO] Tests run: 0, Failures: 0, Errors: 0, Skipped: 0

Fix: Almost always the wrong import: org.junit.Test is JUnit 4 — Surefire is looking for JUnit 5's org.junit.jupiter.api.Test. Also check the class name ends in Test and lives under src/test/java.

JUnit refuses my @BeforeAll
org.junit.platform.commons.JUnitException: @BeforeAll method 'void tests.LoginTest.once()' must be static

Fix: Class-level fixtures run before any instance exists, so they must be static. If you need instance state, you wanted @BeforeEach.

driver is null inside my test
java.lang.NullPointerException: … because "this.driver" is null

Fix: setUp() never ran — usually the same JUnit 4 import problem on @BeforeEach, or your class stopped extending BaseTest during the refactor.

⚡ exercise · refactor to BaseTest
  1. Create BaseTest as above; make your existing test classes extend it and delete their per-test driver code.
  2. Add @DisplayName to every test.
  3. Write a deliberately failing test with a message argument: assertEquals("2", cartCount, "Cart should show 2 after adding two items") — run and see how the message appears in output. Then fix or remove it.
key takeaways
  • @BeforeEach/@AfterEach give every test a fresh browser and guaranteed cleanup.
  • BaseTest + inheritance removes plumbing from test classes.
  • assertEquals(expected, actual) — in that order; assertAll reports multiple failures at once.
← Back to roadmap Data-driven tests →