Selenium with JUnit
JUnit is Java's most widely used unit testing framework, and JUnit 5 (the Jupiter engine) is a common alternative to TestNG for structuring Selenium WebDriver suites, especially in projects already standardized on JUnit for unit tests. JUnit 5 splits into three modules — Jupiter (the new programming model with annotations like @Test), Vintage (backward compatibility for JUnit 3/4), and the Platform (the launcher CI tools and IDEs hook into) — and its annotation set (@BeforeEach, @AfterEach, @BeforeAll, @AfterAll) maps onto Selenium's browser lifecycle in essentially the same way TestNG's does, just with different names and a more extensible architecture via the @ExtendWith mechanism.
Cricket analogy: Just as some franchises stick with the traditional red-ball domestic pathway to develop players while others use T20 academies, teams that already invest in JUnit for unit testing extend that same investment to Selenium rather than adopting a separate framework like TestNG.
JUnit 5 Lifecycle and the Extension Model
@BeforeEach and @AfterEach run before and after every @Test method (equivalent to TestNG's @BeforeMethod/@AfterMethod), while @BeforeAll and @AfterAll run once per test class and must be static unless the class uses @TestInstance(Lifecycle.PER_CLASS). JUnit 5's real architectural advantage is @ExtendWith, which plugs in reusable Extension classes — a common Selenium pattern is a custom WebDriverExtension implementing BeforeEachCallback and AfterEachCallback to inject a managed WebDriver instance via ParameterResolver, so test methods simply declare a WebDriver parameter and never touch driver creation code directly.
Cricket analogy: A modern franchise doesn't build its own fitness program from scratch each season — it plugs in a specialist strength-and-conditioning consultant (an extension) who handles that concern independently; @ExtendWith plugs a WebDriverExtension into JUnit the same modular way.
// JUnit 5 Selenium example
import org.junit.jupiter.api.*;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.By;
import static org.junit.jupiter.api.Assertions.*;
@Tag("smoke")
class LoginJUnitTest {
private WebDriver driver;
@BeforeEach
void setUp() {
driver = new ChromeDriver();
driver.get("https://example.com/login");
}
@Test
@DisplayName("Valid login redirects to dashboard")
void loginWithValidCredentials() {
driver.findElement(By.id("username")).sendKeys("qa_user");
driver.findElement(By.id("password")).sendKeys("Secr3t!");
driver.findElement(By.id("loginBtn")).click();
assertAll("post-login state",
() -> assertTrue(driver.getCurrentUrl().contains("/dashboard")),
() -> assertEquals("Welcome, qa_user",
driver.findElement(By.cssSelector("h1.welcome")).getText())
);
}
@AfterEach
void tearDown() {
if (driver != null) driver.quit();
}
}
JUnit 4 and JUnit 5 (Jupiter) use different packages and are NOT interchangeable: org.junit.Test (JUnit 4) versus org.junit.jupiter.api.Test (JUnit 5). Mixing them in the same class silently produces tests that don't run under the expected runner. If you're on JUnit 4, use @Rule with a WatchMan or ExternalResource for driver management instead of @ExtendWith, which is JUnit 5 only.
Grouping and Reporting with JUnit and Maven
JUnit 5 groups tests using @Tag (e.g., @Tag("smoke")), and the Maven Surefire or Failsafe plugin's <groups> configuration filters which tags run in a given build phase — smoke tags in the fast unit-test phase, and a broader regression tag in a separate integration-test phase against real browsers. Where TestNG bakes reporting in, JUnit 5 relies on the build tool: Surefire produces XML reports Jenkins can parse, and third-party libraries like Allure JUnit5 add rich HTML dashboards with screenshots, matching the reporting depth TestNG gives out of the box.
Cricket analogy: A domestic season is split into a red-ball Ranji phase and a white-ball Vijay Hazare phase, each selecting different squads; Maven's Surefire groups filtering smoke vs regression tags is that same phase-based squad selection.
- JUnit 5 (Jupiter) uses @BeforeEach/@AfterEach and @BeforeAll/@AfterAll, functionally equivalent to TestNG's @BeforeMethod/@AfterMethod and @BeforeClass/@AfterClass.
- @ExtendWith plus a custom Extension (e.g., implementing BeforeEachCallback and ParameterResolver) is the idiomatic way to inject a managed WebDriver into JUnit 5 tests.
- assertAll() groups multiple assertions so all of them run and report together instead of stopping at the first failure.
- JUnit 4 (org.junit) and JUnit 5 (org.junit.jupiter.api) are different APIs and must not be mixed in the same test class.
- @Tag replaces TestNG's groups for categorizing tests (smoke, regression) and is filtered via Maven Surefire/Failsafe configuration.
- JUnit 5 has no built-in HTML report like TestNG; teams typically add Allure or rely on Surefire's XML plus CI parsing.
- Choose JUnit over TestNG mainly when a Java codebase is already standardized on JUnit for unit tests, for tooling consistency.
Practice what you learned
1. Which JUnit 5 annotation is the direct equivalent of TestNG's @BeforeMethod?
2. What is the idiomatic JUnit 5 mechanism for injecting a managed WebDriver instance into test methods?
3. Why should JUnit 4 and JUnit 5 annotations never be mixed in the same test class?
4. What does assertAll() provide that a sequence of individual assertEquals calls does not?
5. How does JUnit 5 categorize tests for selective execution, analogous to TestNG's groups?
Was this page helpful?
You May Also Like
Selenium with TestNG
Learn how TestNG's annotations, grouping, and reporting turn raw Selenium WebDriver scripts into a structured, parallelizable, CI-ready test suite.
Page Object Model (POM)
Understand the Page Object Model design pattern for organizing Selenium tests around reusable, maintainable page classes instead of scattered locators.
Assertions and Soft Assertions
Understand the difference between hard and soft assertions in Selenium test frameworks, and when to use TestNG's SoftAssert or AssertJ.