JUnit Integration and Parameterized Tests
Structuring a Selenium suite with JUnit 5's assertions and lifecycle properly, and running the same test logic across many inputs with @ParameterizedTest.
What you'll learn
- Use JUnit 5 assertions correctly to verify Selenium-driven browser state
- Write a @ParameterizedTest covering several input values with one shared test body
- Explain why parameterized tests report failures more usefully than a hand-written loop inside one test
Prerequisites
Explanation
A Selenium test class built on JUnit 5 (Jupiter) combines this course's driver-lifecycle pattern (@BeforeEach/@AfterEach) with JUnit's standard assertions: assertEquals(expected, actual), assertTrue(condition), assertThrows(SomeException.class, () -> ...). Applied to Selenium specifically, a genuinely useful assertion checks something a real user would actually perceive — assertTrue(driver.findElement(By.id("welcome")).isDisplayed()), assertEquals("Dashboard", driver.getTitle()) — rather than an internal implementation detail that happens to be technically true but doesn't reflect real user-facing correctness.
@ParameterizedTest runs the same test body once per supplied value, exactly the same principle behind parameterized testing in any framework: @ParameterizedTest @ValueSource(strings = {"learner", "instructor", "admin"}) void dashboardShowsCorrectRoleLabel(String role) { ... } generates three distinct, individually-reportable test executions from one method body — a real failure specifically in the "admin" case is reported as dashboardShowsCorrectRoleLabel[3] (or with the actual value shown, depending on configuration), not folded into one ambiguous failure covering all three roles at once. @CsvSource, @MethodSource, and @EnumSource provide richer parameter shapes — multiple parameters per invocation, values computed by a method, or every value of an enum — for cases a plain @ValueSource string/int list can't express.
The concrete, honest reason a hand-written loop inside one test method (for (String role : roles) { ... assertEquals(...) ... }) is worse than @ParameterizedTest, stated precisely: a loop's first failing assertion stops the entire test method immediately (an assertion failure throws), so if the "learner" case fails, you never learn whether "instructor" or "admin" would have passed or failed too — you have to fix the first failure and re-run just to find out about the rest. @ParameterizedTest runs and reports every value's test independently, regardless of whether an earlier one failed, giving you the complete picture — every case's actual pass/fail status — from a single run, not one piece of it at a time across repeated fix-and-rerun cycles.
Example
Modeling the loop-stops-at-first-failure problem versus independently-reported parameterized results, as data.
function runAsLoop(values, checkFn) {
for (const v of values) {
const result = checkFn(v);
if (!result.passed) {
return { stoppedAt: v, remainingUnknown: values.slice(values.indexOf(v) + 1) };
}
}
return { allPassed: true };
}
function runAsParameterized(values, checkFn) {
return values.map((v) => ({ value: v, ...checkFn(v) })); // every value's result, independently
}
function check(role) {
return { passed: role !== "instructor" }; // simulate "instructor" being the one broken case
}
console.log(runAsLoop(["learner", "instructor", "admin"], check));
// { stoppedAt: "instructor", remainingUnknown: ["admin"] } -- "admin" was NEVER actually checked
console.log(runAsParameterized(["learner", "instructor", "admin"], check));
// full, independent results for ALL THREE -- including confirming "admin" genuinely passedTry it yourself
Change check() so 'admin' is the broken case instead, and compare what runAsLoop reveals about 'instructor' in each scenario.
Code editor. Press Escape then Tab to leave the editor if keyboard focus becomes trapped. Press Control+Shift+M inside the editor to toggle Tab-key focus trapping.
Guided exercise
Guided exercise
Write runAsParameterized(values, checkFn) that returns an array with ONE result object per value ({value, passed}), regardless of whether earlier values failed -- modeling @ParameterizedTest's independent-reporting behavior.
Checks: reports a result for every value regardless of earlier failures · confirms a later value's genuine pass/fail status independently
Code editor. Press Escape then Tab to leave the editor if keyboard focus becomes trapped. Press Control+Shift+M inside the editor to toggle Tab-key focus trapping.
Stuck? Get a hint.
Independent exercise
Independent exercise
Write summarizeParameterizedResults(results) where results is an array of {value, passed} objects. Return {passedCount, failedValues} -- the count of passing results, and an array of just the VALUES (not the whole objects) that failed.
Checks: correctly summarizes a mix of passing and failing results · handles an empty results array
Code editor. Press Escape then Tab to leave the editor if keyboard focus becomes trapped. Press Control+Shift+M inside the editor to toggle Tab-key focus trapping.
Stuck? Get a hint.
Common mistakes
- Writing a hand-rolled for-loop with assertions inside one test method instead of using @ParameterizedTest -- the loop stops at the FIRST failing assertion, hiding whether later values would have passed or failed too, requiring repeated fix-and-rerun cycles to find out.
- Asserting on an internal implementation detail (a hidden attribute value, an internal state flag) instead of something a real user would actually perceive (visible text, an element's displayed state) -- this can pass while the actual user-facing behavior is broken.
- Using @ValueSource for parameter shapes it can't express (multiple related parameters per case) instead of reaching for @CsvSource or @MethodSource when the data genuinely needs more structure.
Knowledge check
Takeaway
@ParameterizedTest runs and reports every supplied value's test independently, unlike a hand-written loop that stops entirely at the first failing assertion — and a Selenium assertion is most valuable when it checks something a real user would actually perceive, not an internal implementation detail.
Summary
JUnit 5 assertions (assertEquals, assertTrue, assertThrows) should verify real, user-perceivable state through Selenium. @ParameterizedTest (with @ValueSource, @CsvSource, or @MethodSource) generates independent, individually-reported test executions per value, unlike a hand-written loop that stops at the first failure and hides the status of remaining cases.
References
Your notes
Notes save automatically.
Finished this lesson?
Mark it complete to track your progress and schedule a future review.
AI tutor
The optional AI tutor isn't enabled in this deployment. All lessons, exercises, quizzes, and search work fully without it.