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

Data-driven tests

Test frameworks & structure15 min
as a manual tester

Your test case says: 'Repeat with the data table below' — one case, ten rows.

in automation

@ParameterizedTest runs one test method once per data row. Your spreadsheet instinct, industrialized.

Suppose you must verify error messages for five kinds of bad login. Five copy-pasted tests differing by two strings? The collections lesson promised better: parameterized tests feed rows of data into one method.

One extra dependency in pom.xml (add beside junit-jupiter):

pom.xml
<dependency>
  <groupId>org.junit.jupiter</groupId>
  <artifactId>junit-jupiter-params</artifactId>
  <version>5.11.4</version>
  <scope>test</scope>
</dependency>
InvalidLoginTest.java
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.CsvSource;

public class InvalidLoginTest extends BaseTest {

    @ParameterizedTest(name = "{index}: {0} / {1} → error contains \"{2}\"")
    @CsvSource({
        "locked_out_user, secret_sauce,  locked out",
        "standard_user,   wrong_password, do not match",
        "'',              secret_sauce,  Username is required",
        "standard_user,   '',            Password is required"
    })
    void invalidLoginShowsError(String user, String pass, String expectedError) {
        LoginPage login = new LoginPage(driver);
        login.open();
        login.loginAs(user, pass);
        assertTrue(login.getErrorMessage().contains(expectedError),
                   "Wrong error for " + user);
    }
}
predict: what does this print? think it through, then reveal
output
[INFO] Running InvalidLoginTest
[INFO] Tests run: 4, Failures: 0, Errors: 0, Skipped: 0
[INFO] BUILD SUCCESS

In IntelliJ, each row shows as its own named test:
✓ 1: locked_out_user / secret_sauce → error contains "locked out"
✓ 2: standard_user / wrong_password → error contains "do not match"
✓ 3:  / secret_sauce → error contains "Username is required"
✓ 4: standard_user /  → error contains "Password is required"

One method, four tests — each reported separately, each failing independently. Note the CSV details: '' is an empty string (boundary testing!), and the name= template makes reports human-readable.

The other providers

Where your skill shines: the framework multiplies whatever rows you feed it. Choosing which rows — boundaries, equivalence classes, the nasty empty-string case — is test design, and it's the part you're already good at.
⚠ when it breaks — the classics
Parameterized test explodes before running
org.junit.jupiter.api.extension.ParameterResolutionException: No ParameterResolver registered for parameter…

Fix: The junit-jupiter-params dependency is missing from pom.xml (it's separate from junit-jupiter), or you put @Test on the method as well — it's either/or, use only @ParameterizedTest.

My CSV value contains a comma and everything shifts
expected: <Sauce Labs Backpack> but was: <"Test.allTheThings() T-Shirt>

Fix: Commas split CSV columns. Wrap the whole value in single quotes inside the annotation: "'Test.allTheThings() T-Shirt (Red)', …".

⚡ exercise · data-drive the sort dropdown

Write a parameterized test for saucedemo's product sorting:

  1. Rows: "Name (A to Z)", "Sauce Labs Backpack" / "Name (Z to A)", "Test.allTheThings() T-Shirt (Red)" / "Price (low to high)", "Sauce Labs Onesie".
  2. Method: login → select the sort option by visible text → assert the first .inventory_item_name equals the expected product.
  3. Run and enjoy three named tests from one method.
key takeaways
  • @ParameterizedTest + @CsvSource = one method, N reported tests.
  • '' in CSV = empty string — boundary cases are first-class citizens.
  • @CsvFileSource moves data to a file editable by non-coders.
  • Frameworks multiply rows; your test design chooses them.
← Structuring a test class Fixtures, tags & running subsets →