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")
?
Advertisement
Answer
Ideally speaking, the easiest way to go about doing this would be to:
- Define an abstract class in which you define the required data provider and also the data that your data provider would feed off of, as its source and give it to the test methods (It could be something like the data provider feeds off of an injected value into it)
- Have your test classes, extend this abstract class and then from within an
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:
- The test class uses a Guice Module which injects the required dependencies into the test class.
- The test class exposes the injected dependency to any caller (data provider listener in this case) via the interface
com.rationaleemotions.dynamic.ObjectGetter
- We have an implementation of
org.testng.IAnnotationTransformer
using which we inject a data provider class and a method reference into the test method. - The data provider class is a separate class that implements
com.rationaleemotions.dynamic.ObjectSetter
using which it would get hold of the data it should use for data driven tests. - We create an implementation of
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”.