A @DataProvider is just a method — and like any method it can accept parameters. TestNG can inject two objects into a provider: the java.lang.reflect.Method representing the test method that called it, and the ITestContext carrying suite-level information. Method injection lets one provider serve multiple test methods with different data for each, switching on the method name, its annotations, or any metadata you attach. ITestContext injection lets the provider read environment parameters from testng.xml and load the right dataset for the active run. This lesson covers both, with practical patterns you'll use in real frameworks.
Injecting the calling Method
Declare Method method as the first parameter of your @DataProvider method:
package com.mycompany.tests.data;
import org.testng.annotations.DataProvider;
import java.lang.reflect.Method;
public class DynamicDataProvider {
@DataProvider(name = "dynamicData")
public Object[][] getData(Method method) {
return switch (method.getName()) {
case "testLogin" -> new Object[][] {
{"admin@test.com", "AdminPass123", 200},
{"user@test.com", "UserPass123", 200},
{"wrong@test.com", "BadPass", 401},
};
case "testRegistration" -> new Object[][] {
{"Alice", "alice@test.com", "Password1!", 201},
{"", "empty@test.com", "Password1!", 400},
{"Bob", "bob@test.com", "", 400},
};
case "testPasswordReset" -> new Object[][] {
{"registered@test.com", 200},
{"unknown@test.com", 404},
};
default -> new Object[][] {};
};
}
}The three test methods that share this provider:
package com.mycompany.tests.tests;
import com.mycompany.tests.data.DynamicDataProvider;
import org.testng.Assert;
import org.testng.annotations.Test;
public class AuthApiTest {
@Test(dataProvider = "dynamicData",
dataProviderClass = DynamicDataProvider.class,
description = "Login with various credentials")
public void testLogin(String email, String password, int expectedStatus) {
System.out.printf("Login: %s → expected %d%n", email, expectedStatus);
Assert.assertTrue(true);
}
@Test(dataProvider = "dynamicData",
dataProviderClass = DynamicDataProvider.class,
description = "Register with various name/email/password combos")
public void testRegistration(String name, String email,
String password, int expectedStatus) {
System.out.printf("Register: %s <%s> → expected %d%n", name, email, expectedStatus);
Assert.assertTrue(true);
}
@Test(dataProvider = "dynamicData",
dataProviderClass = DynamicDataProvider.class,
description = "Password reset for known and unknown emails")
public void testPasswordReset(String email, int expectedStatus) {
System.out.printf("Reset: %s → expected %d%n", email, expectedStatus);
Assert.assertTrue(true);
}
}One provider, three methods, three different Object[][] shapes — and each shape matches the parameter list of the method that will receive it. TestNG injects the Method object before calling the provider, so the switch runs once per method and returns the right rows.
Injecting Method to read custom annotations
The real power of Method injection is reading custom annotations to drive data loading declaratively:
// Custom annotation that names the test data file
package com.mycompany.tests.annotation;
import java.lang.annotation.*;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface TestDataFile {
String value(); // path inside testdata/ directory
String sheet() default "Sheet1"; // for Excel
}// Tests annotate themselves with the file they need
@Test(dataProvider = "fileBackedData",
dataProviderClass = DynamicDataProvider.class)
@TestDataFile("testdata/login-scenarios.json")
public void testLoginFromFile(String email, String password, int expectedStatus) {
Assert.assertTrue(true);
}// Provider reads the annotation and loads the right file
@DataProvider(name = "fileBackedData")
public Object[][] fileBackedData(Method method) throws Exception {
TestDataFile annotation = method.getAnnotation(TestDataFile.class);
if (annotation == null) {
throw new IllegalStateException(
method.getName() + " must be annotated with @TestDataFile");
}
String path = annotation.value();
if (path.endsWith(".json")) {
return DataReader.fromJson(path);
} else if (path.endsWith(".csv")) {
return DataReader.fromCsv(path);
} else {
return DataReader.fromExcel(path, annotation.sheet());
}
}Each test now declares its own data source via annotation. The provider is a generic dispatcher — it does not need to know about every test method by name.
Injecting ITestContext
ITestContext carries the current <test> block's configuration from testng.xml — including any <parameter> values. This lets the provider load environment-appropriate data:
import org.testng.ITestContext;
@DataProvider(name = "environmentAwareData")
public Object[][] environmentData(ITestContext context) throws Exception {
String env = context.getCurrentXmlTest().getParameter("environment");
if (env == null) env = "staging";
String dataFile = switch (env) {
case "production" -> "testdata/prod-users.json";
case "staging" -> "testdata/staging-users.json";
default -> "testdata/dev-users.json";
};
System.out.println("Loading data from: " + dataFile + " for env: " + env);
return DataReader.fromJson(dataFile);
}testng.xml with the environment parameter:
<suite name="Regression">
<test name="Staging Tests">
<parameter name="environment" value="staging"/>
<classes>
<class name="com.mycompany.tests.tests.AuthApiTest"/>
</classes>
</test>
</suite>Run against production by changing the parameter value — or override from the CLI:
mvn test -Denvironment=productionCombining Method and ITestContext
Both can be injected together — declare them in this exact order:
@DataProvider(name = "fullInjectionData")
public Object[][] fullData(Method method, ITestContext context) throws Exception {
String env = context.getCurrentXmlTest().getParameter("environment");
TestDataFile annotation = method.getAnnotation(TestDataFile.class);
String basePath = env + "/" + annotation.value();
return DataReader.fromJson(basePath);
}The injection flow
⚠️ Common mistakes
- Declaring
Methodbut not in the first parameter position. TestNG's injection contract requiresMethodas the first parameter, optionally followed byITestContext. Swapping the order or adding other parameters beforeMethodthrows a provider invocation error at runtime. - Switching on method name as strings and misspelling one.
"testLogin"vs"testlogin"returns an emptyObject[][], and TestNG skips the test silently rather than failing loudly. Add adefaultcase that throws anIllegalArgumentException("Unknown method: " + method.getName())to catch typos immediately. - Reading
ITestContext.getCurrentXmlTest()when running outside a suite. Running a single class from IntelliJ bypassestestng.xmlentirely.getCurrentXmlTest()returns a synthetic context with no parameters. Always checkcontext.getCurrentXmlTest().getParameter("key")for null before using it — or pair with an@Optional-style fallback in code.
🎯 Practice task
One provider, multiple methods. 25–35 minutes.
- Create
DynamicDataProviderwithgetData(Method method)returning different data for three method names. Write the three test methods inAuthApiTest. Run — confirm each method receives its own rows. - Test the default case. Add a fourth
@Testthat also usesdynamicDatabut whose name doesn't match anyswitchcase. With thedefault -> new Object[][] {}return, TestNG either skips the test or runs zero invocations. Add adefault -> throw new IllegalArgumentException(...)instead. Run — TestNG should now report aDataProviderHoldererror naming the unregistered method. - Custom annotation dispatch. Define
@TestDataFileand add two JSON files tosrc/test/resources/testdata/. Write afileBackedDataprovider that reads the annotation. Annotate two test methods with different files. Confirm each test loads from its own file. - ITestContext environment switch. Add
<parameter name="environment" value="staging"/>totestng.xml. Write a provider that reads the parameter and prints which data file it's loading. Verify the file path changes when you change the XML value. - Stretch — combine both injections. Make a provider that takes both
Method methodandITestContext context. Use the method name to pick a subdirectory and theenvironmentparameter to pick a file within it. E.g.,staging/login.jsonvsproduction/login.json.
Next lesson: @Factory — when you need multiple instances of the same test class with different constructor arguments, not just different method parameters.