How to use the Data Provider pattern in the project.

Anton Smirnov
4 min readNov 10, 2020
Photo by Norbert Levajsics on Unsplash

In my recent article, I reviewed the main automation patterns that can be applied in your work if necessary. Today I continue a series of articles devoted to the consideration of design patterns. In this article, we’ll talk about Data Patterns and the data provider pattern.

Data Patterns the main goal is to split data and test logic as well as to reduce boilerplate and code duplication in our tests. It should make them more understandable and easier in maintenance for anyone who works with them.

Data Provider.

Data Provider is one of the most widely used data patterns among test engineers. If you want to implement Data-Driven tests and are willing to run the same test logic on multiple sets of data, you could load the data from outer sources (like Excel or CVS table), remote services, or hardcode them in-place. Also, It allows you to write data-driven tests, which means you can run the same test methods multiple times with different data sets.

A very important function of the TestNG framework is DataProvider.

To use the DataProvider function in your tests, you must declare a method with the @DataProvider annotation and then use this method in the test method using the “dataProvider” attribute in the Test annotation.

/**
* Verify country object [ ] [ ].
*
*
@return the object [ ] [ ]
*/
@DataProvider(name = "verifyCountry")
public static Object[][] verifyCountry() {
return new Object[][]{
{"Estonia", "EE", "EST"},
{"Germany", "DE", "DEU"},
{"Afghanistan", "AF", "AFG"}
};
}
@Test(dataProvider = "verifyCountry")
@Story("GET countries from https://restcountries.eu/rest/v2/aplha")
public void testRequestWithSeveralCountries(String name, String alpha2Code, String alpha3Code)

To be able to read data from third-party sources, you can use:

@DataProvider
private static Object[][] testDataProvider() {
try {
return ReadExcelSheet.getTableArray(
"src/main/resources/TestData.xls");
} catch (Exception e) {
return null;
}
}
But I do not recommend using this method because it is a bad practice.

This could be done in the way I showed above by reading from the source and returning untyped data (simple array of arrays or strings). But the modern approach would be to utilize the Value Object pattern, we were talking about previously, and provide data in terms of entities.

For more advanced use of Data Provider in TestNG there is an excellent library Test Data Supplier:

Test Data Supplier.

This repository contains TestNG DataProvider wrapper (the latest version is based on TestNG 7.0.0) which helps to supply test data in a more flexible way.

Common DataProvider forces using a quite old and ugly syntax which expects one of the following types to be returned from DP method’s body:

Object[][]
Iterator<Object[]>

That’s weird, as developers tend to use Stream and Collection API for data manipulation in the modern Java world.

Just imaging if you could use the following syntax to supply some filtered and sorted data into the test method’s signature:

@DataSupplier
public Stream<User> getData() {
return Stream.of(
new User("Petya", "password2"),
new User("Virus Petya", "password3"),
new User("Mark", "password1"))
.filter(u -> !u.getName().contains("Virus"))
.sorted(comparing(User::getPassword));
}

@Test(dataProvider = "getData")
public void shouldSupplyStreamData(final User user) {
// ...
}

If you work with the JUnit framework you can use parameterization.

The @RunWith and @Parameter annotations are used to pass parameter values to the test. @Parameters returns List[]. These values are passed to the constructor as an argument.

/**
* The class Junit test.
*/
@RunWith(value = Parameterized.class)
public class JunitTest {

private String country;

/**
* Instantiates a new Junit test.
*
*
@param country the country
*/
public JunitTest(String country) {
this.country = country;
}

/**
* Verify country collection.
*
*
@return the collection
*/
@Parameters
public static Collection<Object[]> verifyCountry() {
Object[][] data = new Object[][]{{"Estonia"}, {"EE"}, {"EST"}, {"Germany"}};
return Arrays.asList(data);
}

/**
* Push test.
*/
@Test
public void pushTest() {
System.out.println("Parameterized Number is : " + country);
}
}

Unfortunately, there are quite a few restrictions in JUnit:

  • We must follow the JUnit signature to declare the parameter;
  • The parameter is passed to the class constructor to initialize the class field that will contain the parameter value;
  • The return type of the method with the @Parameters annotation must be List []
  • Data must be of a primitive type (String, int)

But there is an alternative. If you are used to the TestNG syntax and would like to use it in a project with JUnit you can use:

JUnit-dataprovider. A TestNG like dataprovider (see here) runner for JUnit having a simplified syntax compared to all the existing JUnit4 features.

/**
* The class Data provider test.
*/
@RunWith(DataProviderRunner.class)
public class DataProviderTest {

/**
* Data provider add object [ ] [ ].
*
*
@return the object [ ] [ ]
*/
@DataProvider
public static Object[][] dataProviderAdd() {
// @formatter:off
return new Object[][]{
{"Estonia", "EE", "EST"},
{"Germany", "DE", "DEU"},
{"Afghanistan", "AF", "AFG"}
/* ... */
};
// @formatter:on
}

/**
* Test add.
*/
@Test
@UseDataProvider("dataProviderAdd")
public void testAdd() {
//some code here
}
}

I love using Data Patterns in my automation, they help me to keep my code healthy and handle resources in the most optimized way possible.

It’s important that we used both patterns (Data Provider and Value Object) in one approach since it helped us to avoid the passing of multiple parameters to a method and to make code cleaner and more readable.

--

--

Anton Smirnov

I’m a software engineer who specializes in testing and automation. My top languages are Java and Swift. My blog is https://test-engineer.tech/