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;
}
}