Back to Chapters

Chapter 5: Parameterized Testing

Using @DataProvider

Parameterized testing is a powerful technique that allows you to run the same test with different sets of data. TestNG provides robust support for parameterized testing through the @DataProvider annotation, which enables you to supply multiple sets of test data to your test methods.

Basic DataProvider Usage

The @DataProvider annotation marks a method as a data provider that supplies data to test methods. Here's a basic example:

import org.testng.Assert;
import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;

public class BasicDataProviderExample {

    @DataProvider(name = "additionData")
    public Object[][] createAdditionData() {
        return new Object[][] {
            {5, 3, 8},    // a, b, expected sum
            {10, 5, 15},
            {-3, 3, 0},
            {0, 0, 0},
            {-5, -5, -10}
        };
    }

    @Test(dataProvider = "additionData")
    public void testAddition(int a, int b, int expected) {
        Calculator calculator = new Calculator();
        int result = calculator.add(a, b);
        Assert.assertEquals(result, expected, "Addition result is incorrect for " + a + " + " + b);
    }
}

class Calculator {
    public int add(int a, int b) {
        return a + b;
    }
}

In this example, the createAdditionData method is annotated with @DataProvider and returns a two-dimensional array of objects. Each inner array represents a set of parameters for the test method. The test method testAddition is executed once for each set of parameters.

Named DataProviders

You can give your data provider a name using the name attribute of the @DataProvider annotation. This name is then referenced in the dataProvider attribute of the @Test annotation:

@DataProvider(name = "multiplicationData")
public Object[][] createMultiplicationData() {
    return new Object[][] {
        {5, 3, 15},    // a, b, expected product
        {10, 5, 50},
        {-3, 3, -9},
        {0, 5, 0},
        {-5, -5, 25}
    };
}

@Test(dataProvider = "multiplicationData")
public void testMultiplication(int a, int b, int expected) {
    Calculator calculator = new Calculator();
    int result = calculator.multiply(a, b);
    Assert.assertEquals(result, expected, "Multiplication result is incorrect for " + a + " * " + b);
}

DataProvider in Different Class

You can also define a data provider in a different class and reference it in your test method:

import org.testng.Assert;
import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;

public class ExternalDataProviderExample {

    @Test(dataProvider = "divisionData", dataProviderClass = CalculatorDataProviders.class)
    public void testDivision(int a, int b, double expected) {
        Calculator calculator = new Calculator();
        double result = calculator.divide(a, b);
        Assert.assertEquals(result, expected, 0.001, "Division result is incorrect for " + a + " / " + b);
    }
}

class CalculatorDataProviders {

    @DataProvider(name = "divisionData")
    public static Object[][] createDivisionData() {
        return new Object[][] {
            {10, 2, 5.0},    // a, b, expected quotient
            {15, 3, 5.0},
            {-6, 3, -2.0},
            {0, 5, 0.0}
        };
    }
}

class Calculator {
    public double divide(int a, int b) {
        if (b == 0) {
            throw new ArithmeticException("Division by zero");
        }
        return (double) a / b;
    }
}

Parallel Execution with DataProvider

You can run data provider invocations in parallel by setting the parallel attribute to true:

import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;

public class ParallelDataProviderExample {

    @DataProvider(name = "testData", parallel = true)
    public Object[][] createData() {
        return new Object[][] {
            {"Test 1", 1},
            {"Test 2", 2},
            {"Test 3", 3},
            {"Test 4", 4}
        };
    }

    @Test(dataProvider = "testData")
    public void testMethod(String name, int value) {
        System.out.println("Running " + name + " with value " + value +
                           " on thread " + Thread.currentThread().getId());
        // Test logic
        try {
            Thread.sleep(1000); // Simulate work
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    }
}

XML Parameter Configuration

TestNG allows you to pass parameters from the TestNG XML file to test methods using the @Parameters annotation.

Basic Parameter Usage

Here's a basic example of using parameters from an XML file:

import org.testng.Assert;
import org.testng.annotations.Parameters;
import org.testng.annotations.Test;

public class XmlParameterExample {

    @Parameters({"username", "password"})
    @Test
    public void testLogin(String username, String password) {
        System.out.println("Logging in with username: " + username + " and password: " + password);

        // Simulate login logic
        boolean loginSuccess = login(username, password);

        Assert.assertTrue(loginSuccess, "Login should succeed with valid credentials");
    }

    private boolean login(String username, String password) {
        // Simulate login validation
        return "testuser".equals(username) && "testpass".equals(password);
    }
}

Corresponding TestNG XML file:

<!DOCTYPE suite SYSTEM "https://testng.org/testng-1.0.dtd">
<suite name="ParameterizedSuite">
    <test name="ParameterizedTest">
        <parameter name="username" value="testuser"/>
        <parameter name="password" value="testpass"/>
        <classes>
            <class name="XmlParameterExample"/>
        </classes>
    </test>
</suite>

Parameter Inheritance

Parameters defined at the suite level are inherited by all tests in the suite:

<!DOCTYPE suite SYSTEM "https://testng.org/testng-1.0.dtd">
<suite name="ParameterizedSuite">
    <!-- Suite-level parameters -->
    <parameter name="browser" value="chrome"/>
    <parameter name="baseUrl" value="https://example.com"/>

    <test name="LoginTest">
        <!-- Test-specific parameters -->
        <parameter name="username" value="testuser"/>
        <parameter name="password" value="testpass"/>
        <classes>
            <class name="LoginTest"/>
        </classes>
    </test>

    <test name="RegistrationTest">
        <!-- Test-specific parameters -->
        <parameter name="email" value="test@example.com"/>
        <classes>
            <class name="RegistrationTest"/>
        </classes>
    </test>
</suite>

Java code for the above XML:

import org.testng.annotations.Parameters;
import org.testng.annotations.Test;

public class LoginTest {

    @Parameters({"browser", "baseUrl", "username", "password"})
    @Test
    public void testLogin(String browser, String baseUrl, String username, String password) {
        System.out.println("Testing login on " + browser + " at " + baseUrl);
        System.out.println("Using credentials: " + username + " / " + password);
        // Test logic
    }
}

public class RegistrationTest {

    @Parameters({"browser", "baseUrl", "email"})
    @Test
    public void testRegistration(String browser, String baseUrl, String email) {
        System.out.println("Testing registration on " + browser + " at " + baseUrl);
        System.out.println("Using email: " + email);
        // Test logic
    }
}

Optional Parameters

You can mark parameters as optional using the @Optional annotation:

import org.testng.annotations.Optional;
import org.testng.annotations.Parameters;
import org.testng.annotations.Test;

public class OptionalParameterExample {

    @Parameters({"browser"})
    @Test
    public void testWithRequiredParameter(String browser) {
        System.out.println("Testing on browser: " + browser);
        // Test logic
    }

    @Parameters({"browser", "version"})
    @Test
    public void testWithOptionalParameter(String browser, @Optional("latest") String version) {
        System.out.println("Testing on browser: " + browser + ", version: " + version);
        // Test logic
    }
}

External Data Sources

TestNG can work with various external data sources for parameterized testing.

CSV Data Source

You can read test data from CSV files:

import org.testng.Assert;
import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;

import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

public class CsvDataProviderExample {

    @DataProvider(name = "csvData")
    public Object[][] createDataFromCsv() throws IOException {
        List<Object[]> data = new ArrayList<>();

        try (BufferedReader br = new BufferedReader(new FileReader("src/test/resources/test-data.csv"))) {
            String line;
            // Skip header line
            br.readLine();

            while ((line = br.readLine()) != null) {
                String[] values = line.split(",");
                data.add(new Object[] {
                    Integer.parseInt(values[0].trim()),
                    Integer.parseInt(values[1].trim()),
                    Integer.parseInt(values[2].trim())
                });
            }
        }

        return data.toArray(new Object[0][]);
    }

    @Test(dataProvider = "csvData")
    public void testAddition(int a, int b, int expected) {
        Calculator calculator = new Calculator();
        int result = calculator.add(a, b);
        Assert.assertEquals(result, expected, "Addition result is incorrect for " + a + " + " + b);
    }
}

Example CSV file (test-data.csv):

a,b,expected
5,3,8
10,5,15
-3,3,0
0,0,0
-5,-5,-10

Database Data Source

You can retrieve test data from a database:

import org.testng.Assert;
import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;

import java.sql.*;
import java.util.ArrayList;
import java.util.List;

public class DatabaseDataProviderExample {

    @DataProvider(name = "databaseData")
    public Object[][] createDataFromDatabase() throws SQLException {
        List<Object[]> data = new ArrayList<>();

        String url = "jdbc:mysql://localhost:3306/testdb";
        String user = "testuser";
        String password = "testpass";

        String query = "SELECT a, b, expected FROM calculator_test_data";

        try (Connection conn = DriverManager.getConnection(url, user, password);
             Statement stmt = conn.createStatement();
             ResultSet rs = stmt.executeQuery(query)) {

            while (rs.next()) {
                data.add(new Object[] {
                    rs.getInt("a"),
                    rs.getInt("b"),
                    rs.getInt("expected")
                });
            }
        }

        return data.toArray(new Object[0][]);
    }

    @Test(dataProvider = "databaseData")
    public void testAddition(int a, int b, int expected) {
        Calculator calculator = new Calculator();
        int result = calculator.add(a, b);
        Assert.assertEquals(result, expected, "Addition result is incorrect for " + a + " + " + b);
    }
}

Excel Data Source

You can read test data from Excel files using libraries like Apache POI:

import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.testng.Assert;
import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;

import java.io.FileInputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

public class ExcelDataProviderExample {

    @DataProvider(name = "excelData")
    public Object[][] createDataFromExcel() throws IOException {
        List<Object[]> data = new ArrayList<>();

        try (FileInputStream fis = new FileInputStream("src/test/resources/test-data.xlsx");
             Workbook workbook = new XSSFWorkbook(fis)) {

            Sheet sheet = workbook.getSheetAt(0);
            Iterator<Row> rowIterator = sheet.iterator();

            // Skip header row
            if (rowIterator.hasNext()) {
                rowIterator.next();
            }

            while (rowIterator.hasNext()) {
                Row row = rowIterator.next();
                data.add(new Object[] {
                    (int) row.getCell(0).getNumericCellValue(),
                    (int) row.getCell(1).getNumericCellValue(),
                    (int) row.getCell(2).getNumericCellValue()
                });
            }
        }

        return data.toArray(new Object[0][]);
    }

    @Test(dataProvider = "excelData")
    public void testAddition(int a, int b, int expected) {
        Calculator calculator = new Calculator();
        int result = calculator.add(a, b);
        Assert.assertEquals(result, expected, "Addition result is incorrect for " + a + " + " + b);
    }
}

Parameterization with Java 21 Records

Java 21's record classes can significantly enhance parameterized testing by providing a more structured and type-safe approach to test data.

Using Records as Test Data

import org.testng.Assert;
import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;

public class RecordParameterizedExample {

    // Define a record for test data
    record CalculationTestCase(int a, int b, int expected, String operation) {}

    @DataProvider(name = "calculationData")
    public Object[][] createCalculationData() {
        return new Object[][] {
            {new CalculationTestCase(5, 3, 8, "add")},
            {new CalculationTestCase(10, 5, 5, "subtract")},
            {new CalculationTestCase(4, 3, 12, "multiply")},
            {new CalculationTestCase(10, 2, 5, "divide")}
        };
    }

    @Test(dataProvider = "calculationData")
    public void testCalculation(CalculationTestCase testCase) {
        Calculator calculator = new Calculator();
        int result = switch (testCase.operation()) {
            case "add" -> calculator.add(testCase.a(), testCase.b());
            case "subtract" -> calculator.subtract(testCase.a(), testCase.b());
            case "multiply" -> calculator.multiply(testCase.a(), testCase.b());
            case "divide" -> (int) calculator.divide(testCase.a(), testCase.b());
            default -> throw new IllegalArgumentException("Unknown operation: " + testCase.operation());
        };

        Assert.assertEquals(result, testCase.expected(),
                           testCase.operation() + " result is incorrect for " +
                           testCase.a() + " and " + testCase.b());
    }
}

class Calculator {
    public int add(int a, int b) {
        return a + b;
    }

    public int subtract(int a, int b) {
        return a - b;
    }

    public int multiply(int a, int b) {
        return a * b;
    }

    public double divide(int a, int b) {
        if (b == 0) {
            throw new ArithmeticException("Division by zero");
        }
        return (double) a / b;
    }
}