If I have a method:
@Test(dataProvider = "webTarget")
void testFirst(WebTarget target) {
// ...
}
Can I create a Listener or something in TestNG, that if I have a method:
@Test
void testFirst(WebTarget target) {
// ...
}
then it automatically injects specific dataProvider, without explicitly specifying it @Test(dataProvider = "webTarget")
?
Ideally speaking, the easiest way to go about doing this would be to:
org.testng.IAnnotationTransformer
implementation, you merely inject the data provider method name into the test class.In case you don't want to use an abstract class also, then here's another alternative. This kind of looks like a round about way of doing it.
For this example, the dependency injection framework that I am using is Guice.
The interfaces that we are going to be using in this example are as below
/**
* Lets any test class expose the injected values to any caller.
*/
public interface ObjectGetter {
/**
* @return - The {@link Student} object that is required.
*/
Student getStudent();
}
/**
* Allows for setting the actual object to be used by any data provider
*/
public interface ObjectSetter {
/**
* @param student - The {@link Student} object
*/
void setStudent(Student student);
}
Here's how the Guice module that we are using in this example looks like
import com.google.inject.Binder;
import com.google.inject.Module;
public class MyLocalGuiceModule implements Module {
@Override
public void configure(Binder binder) {
binder.bind(Student.class).toInstance(new Student(100, "KungFu-Panda"));
}
}
Here's how the test class looks like
import com.google.inject.Inject;
import static org.assertj.core.api.Assertions.assertThat;
import org.testng.annotations.Guice;
import org.testng.annotations.Test;
@Guice(modules = MyLocalGuiceModule.class)
public class SampleTestClass implements ObjectGetter {
@Inject
private Student student;
@Override
public Student getStudent() {
return student;
}
@Test
public void testMethod(Student s) {
String text = s.toString();
assertThat(text).isEqualTo("Student{id=100, name='KungFu-Panda'}");
}
}
Here's how the separate data provider class would look like
import org.testng.annotations.DataProvider;
public class DataProviderHouse implements ObjectSetter {
private Student student;
@DataProvider(name = "students")
public Object[][] getStudents() {
return new Object[][]
{
{student}
};
}
@Override
public void setStudent(Student student) {
this.student = student;
}
}
The annotation transformer looks like below:
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import org.testng.IAnnotationTransformer;
import org.testng.annotations.ITestAnnotation;
public class LocalTransformer implements IAnnotationTransformer {
@Override
public void transform(ITestAnnotation annotation, Class testClass, Constructor testConstructor,
Method testMethod) {
annotation.setDataProviderClass(DataProviderHouse.class);
annotation.setDataProvider("students");
}
}
The data provider listener looks like below:
import org.testng.IDataProviderListener;
import org.testng.IDataProviderMethod;
import org.testng.ITestContext;
import org.testng.ITestNGMethod;
public class DataProviderListener implements IDataProviderListener {
@Override
public void beforeDataProviderExecution(IDataProviderMethod dataProviderMethod,
ITestNGMethod method, ITestContext iTestContext) {
Object dpInstance = dataProviderMethod.getInstance();
if (!(dpInstance instanceof ObjectSetter)) {
return;
}
Object testInstance = method.getInstance();
if (!(testInstance instanceof ObjectGetter)) {
return;
}
((ObjectSetter) dpInstance).setStudent(((ObjectGetter) testInstance).getStudent());
}
}
Here's how the suite xml would look like
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE suite SYSTEM "https://testng.org/testng-1.0.dtd">
<suite name="dynamic_data_provider_suite" verbose="2">
<listeners>
<listener class-name="com.rationaleemotions.dynamic.LocalTransformer"/>
<listener class-name="com.rationaleemotions.dynamic.DataProviderListener"/>
</listeners>
<test name="dynamic_data_provider_test" verbose="2">
<classes>
<class name="com.rationaleemotions.dynamic.SampleTestClass"/>
</classes>
</test>
</suite>
Here's the chain of events that are expected to happen:
com.rationaleemotions.dynamic.ObjectGetter
org.testng.IAnnotationTransformer
using which we inject a data provider class and a method reference into the test method.com.rationaleemotions.dynamic.ObjectSetter
using which it would get hold of the data it should use for data driven tests.org.testng.IDataProviderListener
which TestNG provides to eavesdrop into before and after invocation events for data providers. Using this listener we extract out the Guice injected data from the test class and then inject it back into the object to which the data provider belongs to.This is a bit long way of doing this, but goes a bit more to making the data providers truly dynamic.
Your mileage on using is likely to vary depending upon the actual use case in which you would like to employ such a "sophisticated but yet convoluted approach".