How to Use DataProvider in TestNG for Automated Testing (With Examples)

Posted Nov 4th, 2022

Learn the fundamentals of DataProvider and how it can be used in TestNG for automated testing.

As the volume of applications increases, software testing teams find themselves overburdened with a “combinatorial explosion” of permutations for their test cases. In the early days, we hard-coded our test cases with the most important workflows, and we wrote bespoke automation scripts, each one to handle its own particular use case. We probably still do this more than we should if we’re being honest.

We need to up-level our thinking. What if, instead of 15 scripts to handle 15 slightly different scenarios, we wrote one script, but then injected 15 (or 50 or 500) sets of data into it? That would allow us to iterate over the same script while plugging in different variations of data. The script might become slightly more complicated to handle different outcomes, but those outcomes can be part of the data we inject–this way, we can reuse code, cover more bases, and mitigate more risk.

TestNG facilitates this with a feature called the DataProvider. This post will walk you through the fundamentals of the TestNG DataProvider, and explain how it can make one test into a hundred (or more!).

What is TestNG?

TestNG is a powerful Java automated testing framework, offering test cases that are organized, legible, manageable, and user-friendly. It allows sophisticated test case management, tagging/grouping of tests, parallel execution, robust reporting, integration with all major CI/CD platforms, event-driven actions, and it has a large and vibrant community supporting it.

Our examples will show test cases created using Selenium commands and written in Java. 

When performing cross-browser testing across numerous devices, browsers, and versions, TestNG provides scalable automation testing capabilities through the DataProvider, allowing you to run one snippet of code multiple times with the DataProvider, on hundreds of browser/OS combinations. This way you can turn one snippet of code into an army!

Why Use DataProvider in TestNG?

Testers use a DataProvider to pass strings or more complex parameters (such as objects obtained from a property file or a database) into a test method. Normally, test methods have void parameters:

@Test
publicvoidnormalTest() {
   //do something normal
}

The test is then obligated to use only what it already has access to, which typically means a bunch of data creation statements declared in local variables (or hard-coded into the other method calls):

@Test
   publicvoidloginTest() {
      String username = “first.last”;
      String password = “abc@123$pass”;
      loginPage.login(username, password);
      Assert.assertTrue( <something indicating the user logged in> );
}

This results in a series of tests that look similar to each other, with slight variations on the data you’re creating, like loginTest1, loginTest2, loginTest3 (or hopefully more descriptive names like loginValidUser, loginInvalidUser, loginAdmin).

With TestNG’s DataProvider, you can write this piece of code only once (including whether or not it should fail), and then make the data itself do the heavy lifting. This means much less code to debug, much easier diagnosis of failure, and the possibility of having non-coders work through the test cases–all they’d have to do is manage the DataProviders input.

This accelerates test development while improving the accuracy of the test cases, especially when you execute the tests in parallel. For example, when using a DataProvider, a single execution of a test suite enables you to pass multiple parameters in the form of objects, words, vectors, or numbers.

Types of Parameterization in TestNG

TestNG employs two techniques for passing parameters to your tests:

  • TestNG parameterization via testng.xml (or testng.yml–these examples will use xml)

  • TestNG parameterization via DataProvider method (in code)

TestNG parameterization via TestNG.xml

Using TestNG parameterization via testng.xml, you construct basic parameters in the testng.xml file, then supply values to any test function's argument list in the source files. The parameters specified in the testng.xml file are then referenced through the @Parameters annotation.

This method is preferred if you have to generate the data for your tests programmatically, or if the people maintaining your tests are uncomfortable working with the Java code directly. TestNG offers both XML and YML formats, which you can use interchangeably at your comfort level.

Here’s an example of how to use the testng.xml approach for search for products in an imaginary eCommerce site:

  • There’s a class called TestProductSearch.java, which has access to basic search methods (searchProduct and productExists).

  • We add a test method–searchTest()–which should accept a string representing the product we’re looking for as a parameter, and a boolean representing whether the product is valid or not (this allows you to test positive and negative cases).

  • This test will be executed 3 times: once per “test” entry in testng.xml. Note that, since we’re providing a boolean variable indicating whether or not we expect the search to return a positive or negative result, we have a method called productExists that will return true or false to that effect.

  • Include the @Parameters("products") annotation, which references the names of the products supplied from the testng.xml file.

  • Define the productName attribute and its corresponding value in the testng.xml file.

//TestProductSearch.java file
package sampleTestNGPackage;
import org. testng.annotations.Parameters;
import org. testng.annotations.Test;
public class TestProductSearch {
  @Test
  @Parameters("products")
  public void searchTest(String productName, boolean shouldExist) {
     
// searches the site for the product specified
searchProduct(productName);
Assert.assertEquals(productExists(productName), shouldExist);

   }
}

This is what our sample testNG.xml file looks like:

//testng.xml
<?xml version = "1.0" encoding = "UTF-8"?>
<!DOCTYPE suite SYSTEM "http://testng.org/testng-1.0.dtd" >

<suite name = "productSearchSuite">
   <test name = "testPencilSkirt">
 <parameter name = "productName" value="pencil skirt"/>
 <parameter name = "shouldExist" value="true"/>
 <classes>
       <classname = "sampleTestNGPackage.TestProductSearch" />
 </classes>
   </test>

   <test name = "testBackpack">
 <parameter name = "browserName" value="backpack"/>
 <parameter name = "shouldExist" value="true"/>
 <classes>
       <classname = "sampleTestNGPackage.TestProductSearch" />
 </classes>
   </test>

   <test name = "testFrenchBread">
 <parameter name = "browserName" value="French Bread"/>
 <parameter name = "shouldExist" value="false"/>
 <classes>
       <classname = "sampleTestNGPackage.TestProductSearch" />
 </classes>
   </test>
</suite>

Imagine how complicated a search engine like this would actually be to test. Then imagine how much redundant work would have to be performed if you had to hard code all of the tests with large datasets and parameters into a single file.

Better yet, this testng.xml file could be auto-generated by someone on the Business Analytics team, allowing you to test a different set of search results every time, using real-world user data!

TestNG parameterization via DataProvider

Sometimes it’s best to just work in the code. If you’re modeling your test cases in the same place, and the team is comfortable managing the test data there, here’s how it’s done:

@DataProvider (name = "name_of_dataprovider", parallel = true)
public Object [] [] testMethodName() {
returnnew Object [] [] {
    { "First_testcase_value" },
    { "second_testcase_value" },
    { "Nth_testcase_value" }
   }
}

In the syntax above, the @DataProvider annotation indicates that the DataProvider method is a separate function from the test method, and takes the default parameters:

  • Name: The name of a specific data provider. name is also used to request data via the @Test method annotated with the @DataProvider. If the name argument in @DataProvider is left blank, the DataProvider’s method name will be used by default.

  • Parallel: If this is set to true, the test method annotated with @DataProvider will run concurrently. The default value is false, which means that it will execute serially.

NOTE: The “parallel” parameter here will actually fork a separate TestNG process, without adhering to the “thread-count” parameter in your testng.xml file. For example, if thread-count specified 10, and each of your test cases has 10 rows of DataProvider input, you will actually run 100 tests in parallel! This can be a good thing, but make sure it’s well-understood within the concurrency limitations of your execution platform.

The DataProvider method must be set to return a 2D array of objects – hence the Object [ ] [ ] in the declaration.

How to Use DataProvider in TestNG (with Code Examples)

You can use a DataProvider in a variety of ways, depending on your use case and business logic. Let's take a look at some real examples of how a DataProvider can be used in TestNG.

Defining DataProvider in TestNG within the same class

This is the most straightforward method of utilizing the DataProvider. In this case, we define the DataProvider (annotated with @DataProvider) and test methods (annotated with @Test) within the class.

  • First, we'll make a Java test class called DataProviderTester.java.

  • Within the same class, we will define a method called getUserDetails() that uses the @DataPovider notation and returns a 2D array of the user object.

  • We then write a testUser() method with the @Test notation that maps to the previous method and prints out the user data.

The code for this is as follows:

//DataProviderTester.java

import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;
import User from ./src/User.java;
public class DataProviderTester {

   @DataProvider(name = "user_test1")
   public static Object[][] getUserDetails() {
       returnnew Object[][] { { new User("Jane Done","Female",21) } };
   }
   @Test(dataProvider = "user_test1")
   public void testUser(User myUser) {
     System.out.println(myUser.name + " " + myUser.gender + " " + myUser.age);
   } 
}

NOTE: This example actually sends instantiated Java Objects to the test method, which can give you a lot more power and flexibility than hard-coded strings, integers, or other simple data types.

Inheriting DataProvider in TestNG

DataProvider can also be inherited, in which case the Test and DataProvider methods are written in separate classes. This is a common strategy when writing code (particularly when dealing with huge datasets and various test cases) because it helps with code reusability, modularization, and readability.

The following code shows you how to construct a utility class (dataProviderClass) that maintains multiple DataProviders. Importantly, it can also be used to pass test data via the class attribute.

//DataProviderClass.java
package  sampleNestedPackage;
import org.testng.annotations.DataProvider;

public class DataProviderClass {
  
  @DataProvider(name = "data-provider")
   public static Object [] [] dataProviderMethod () {

     return new Object[][] { { "first Object " }, { "second Object" } };
   }
}
// TestClass.java
package  sampleNestedPackage;
import org.testng.annotations.Test;

public class TestClass {
  
  @Test(dataProvider = "data-provider", dataProviderClass= DataProviderClass.class )
   public static Object [] [] testMethod (String data) {
        System.out.println(data)
   }
}

Passing multiple parameter values to DataProvider

We can also pass multiple parameter values to DataProvider as a single argument so that a string of inputs can be executed in one go. For example, the following test case checks whether the third argument is equal to the sum of the first two integers:

//DProviderClass.java

import org.testng.annotations.Test;
import org.testng.annotations.DataProvider;
import org.testng.Assert;

public class DProviderClass {
  
  @DataProvider(name = "data-provider")
   public Object [] [] dpMethod (String data) {
                returnnew Object [] [] {{13,3,16},{3,6,9},{4,7,10}};
   }

  @Test(dataProvider = "data-provider")
   public void myTest (int numOne, int numTwo, int theSum ) {
                
                int sum = numOne + numTwo;

                Assert.AssertEquals(theSum, sum);
   }
}

As you can see in the code above, we utilized DataProvider in TestNG to aggregate multiple values into a single parameter rather than assigning values one at a time.

Getting Started with DataProvider in TestNG for Automated Testing

In this article, we showed you how to use TestNG parameters like DataProvider to execute test scripts. When you have a large number of permutations and combinations to test, you can use TestNG’s DataProvider instead of hard coding the test value in the scripts.

Since TestNG can accept external files, DataProvider can be used to run tests with numerous parameters, which is particularly useful with functional test automation frameworks like Selenium.

Organizations should aim to optimize testing by running parallel tests and leveraging automated testing through TestNG.

Sauce Labs can help you with a wide variety of automated testing needs. Sign up for a Sauce Labs free trial to start testing today.


Written by

Marcus Merrell


Topics

Automated TestingUnit Testing

Categories


Try it free