How to Parameterize tests in JUnit4

HOME

JUnit 4 has a feature called parameterized tests. Parameterized test means to execute the test multiple time with different sets of test data. This eliminates the redundancy of the code. This helps the developers to save time by eliminating the need to copy the code multiple times. Parameterizing tests can increase code coverage and provide confidence that the code is working as expected. These are the steps that need to be followed to create a parameterized test.

  • Annotate test class with @RunWith(Parameterized.class).
  • Create an instance variable for each “column” of test data.
  • It has a single constructor that contains the test data.
  • Create a public static method annotated with @Parameters that returns a Collection of Objects (as Array) as test data set.
  • Create your test case(s) using the instance variables as the source of the test data.

In a Maven project, to parameterize the tests in JUnit4, we need to add a dependency to POM.xml.

 <dependency>
      <groupId>org.junit.jupiter</groupId>
      <artifactId>junit-jupiter-params</artifactId>
      <version>5.8.2</version>
      <scope>test</scope>
</dependency>

The test case will be invoked once for each row of data.

There are multiple ways to parameterize a test. They are the following:

  1. Parameterized Test with Constructor
  2. Parameterized Test with Parameter Annotation
  3. Parameterized Test using CSV File

Let us see parameterized tests in action.

1. Parameterized Test with Constructor

Steps to create a Parameterized JUnit test

1. Create a parameterized test class

Annotate your test class using @runWith(Parameterized.class).

Declaring the variable ‘num1’, ‘num2’, ‘num3’ as private and type as int.

@RunWith(value = Parameterized.class)
public class ParameterizedTest {

    private int num1;
    private int num2;
    private int num3;

2. Create a constructor that stores the test data. It stores 3 variables.

    public ParameterizedTest(int num1, int num2, int num3) {
        this.num1 = num1;
        this.num2 = num2;
        this.num3 = num3;
    }

3. Create a static method that generates and returns test data.

Creating a two-dimensional array (providing input parameters for multiplication). Using the asList method, we convert the data into a List type. Since the return type of method input is the collection.

Using @Parameters annotation to create a set of input data to run our test.

    @Parameterized.Parameters(name = "{index}: multiply({0}*{1}) = {2}")
    public static Collection<Object[]> data() {
        return Arrays.asList(new Object[][]{
                {1, 1, 1},
                {2, 2, 4},
                {8, 2, 16},
                {4, 5, 20},
                {5, 5, 25}
        });
    }

The static method identified by @Parameters annotation returns a Collection, where each entry in the Collection will be the input data for one iteration of the test.

The complete code is shown below:

import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import java.util.Arrays;
import java.util.Collection;
import static org.junit.Assert.assertEquals;

@RunWith(value = Parameterized.class)
public class ParameterizedTest {

    private int num1;
    private int num2;
    private int num3;

    public ParameterizedTest(int num1, int num2, int num3) {
        this.num1 = num1;
        this.num2 = num2;
        this.num3 = num3;
    }

    @Parameterized.Parameters(name = "{index}: multiply({0}*{1}) = {2}")
    public static Collection<Object[]> data() {
        return Arrays.asList(new Object[][]{
                {1, 1, 1},
                {2, 2, 4},
                {8, 2, 16},
                {4, 5, 20},
                {5, 5, 25}
        });
    }

    @Test
    public void multiplication() {
        System.out.println("The product of "+num1+" and "+num2+" is "+num3);
        assertEquals((num1*num2), num3);
    }

}

The output of the above program is

2. Parameterized Test with Parameter Annotation

It is also possible to inject data values directly into fields without needing a constructor using the @Parameter annotation.

import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import java.util.Arrays;
import java.util.Collection;
import static org.junit.Assert.assertEquals;

@RunWith(value = Parameterized.class)
public class ParameterizedTest1 {

    @Parameterized.Parameter(value = 0)
    public int num1;

    @Parameterized.Parameter(value = 1)
    public int num2;

    @Parameterized.Parameter(value = 2)
    public int num3;

    @Parameterized.Parameters(name = "{index}: multiply({0}*{1}) = {2}")
    public static Collection<Object[]> data() {
        return Arrays.asList(new Object[][]{
                {1, 1, 1},
                {2, 2, 4},
                {8, 2, 16},
                {4, 5, 20},
                {5, 5, 24}
        });
    }

    @Test
    public void multiplication() {
        System.out.println("The product of "+num1+" and "+num2+" is "+num3);
        assertEquals((num1*num2), num3);
    }
}

The output of the above program is

3. Parameterized Test using CSV File

We can use an external CSV file to load the test data. This helps if the number of possible test cases is quite significant, or if test cases are frequently changed. The changes can be done without affecting the test code.

To start with, add a JUnitParams dependency to POM.xml

<dependency>
     <groupId>pl.pragmatists</groupId>
     <artifactId>JUnitParams</artifactId>
     <version>1.1.1</version>
     <scope>test</scope>
</dependency>

Let’s say that we have a CSV file with test parameters as JunitParamsTestParameters.csv:

Now let’s look at how this file can be used to load test parameters in the test method:

import junitparams.JUnitParamsRunner;
import org.junit.Test;
import org.junit.runner.RunWith;
import junitparams.FileParameters;
import static org.junit.Assert.assertEquals;

@RunWith(JUnitParamsRunner.class)
public class ParameterizedTest2 {

    @Test
    @FileParameters("src/test/resources/JunitParamsTestParameters.csv")
    public void multiplication(int num1, int num2, int num3) {
        System.out.println("The product of "+num1+" and "+num2+" is "+num3);
        assertEquals((num1*num2), num3);
    }
}

The output of the above program is

The parameterized test enables us to execute the same test over and over again using different values.

Important annotations to be used during parameterization

  • @RunWith
  • @Parameters

Congratulations. We are done. I hope this tutorial is helpful to you. Happy Learning!!

Advertisement

How to run parameterized Selenium test using JUnit5

HOME

The previous tutorial has shown the various parameterized tests in JUnit5. This tutorial shows how to run a test multiple times with a different set of data. This helps to reduce the duplication of code. This is a very common scenario in any testing. Imagine, we want to test the requirement for a login page that uses a username and password to log in to the application. Username and password must satisfy some conditions like username can be only alphabets and no numeric and special characters. There could be multiple sets of data that can be used to test this requirement.

Pre-Requisite:

  1. Selenium – 3.141.59
  2. Maven
  3. Java 11
  4. JUnit Jupiter Engine – 5.8.2
  5. JUnit Jupiter API- 5.8.2

JUnit5 provides a lot of ways to parameterize a test – @ValueSource, @EnumSource, @MethodSource, @CsvSource, @CsvFileSource, and @ArgumentsSource.

Let us see an example where the test is not parameterized. In the below example, we want to verify the different error messages generated by passing incorrect values to username and password.

This is the base class – Login which contains the test method that uses a different set of test data.

import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.support.FindBy;
import org.openqa.selenium.support.PageFactory;

public class LoginPage {

    WebDriver driver ;

    @FindBy(name="txtUsername")
    WebElement username;

    @FindBy(name="txtPassword")
    WebElement password;

    @FindBy(id="btnLogin")
    WebElement loginButton;

    @FindBy(id="spanMessage")
    WebElement actualErrorMessage;


    public LoginPage(WebDriver driver) {

        this.driver = driver;

        // This initElements method will create all WebElements
        PageFactory.initElements(driver, this);
    }

    public void setUserName(String strUserName) {
        username.sendKeys(strUserName);
    }

    // Set password in password textbox
    public void setPassword(String strPassword) {
        password.sendKeys(strPassword);
    }

    // Click on login button
    public void clickLogin() {
        loginButton.click();
    }

    // Get the error message
    public String getErrorMessage() {
        return actualErrorMessage.getText();
    }

    public void login(String strUserName, String strPasword) {

        // Fill user name
        this.setUserName(strUserName);

        // Fill password
        this.setPassword(strPasword);

        // Click Login button
        this.clickLogin();
    }

}

The below example shows 4 tests using a common test with 4 different sets of data.

import io.github.bonigarcia.wdm.WebDriverManager;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.chrome.ChromeOptions;
import static org.junit.jupiter.api.Assertions.assertEquals;

public class NonParameterizedLoginTest  {

    WebDriver driver;
    LoginPage login;

    @BeforeEach
    void setUp() {

        WebDriverManager.chromedriver().setup();
        ChromeOptions chromeOptions = new ChromeOptions();
        driver = new ChromeDriver(chromeOptions);
        driver.manage().window().maximize();
        driver.get("https://opensource-demo.orangehrmlive.com/");

    }

    @Test
    void invalidCredentials1() {

        login = new Login(driver);
        login.login("Admin","Admin");
        String actualErrorMessage = login.getErrorMessage();
        assertEquals("Invalid credentials", actualErrorMessage);

    }

    @Test
    void invalidCredentials2() {

        login = new Login(driver);
        login.login("","Admin");
        String actualErrorMessage = login.getErrorMessage();
        assertEquals("Username cannot be empty", actualErrorMessage);

    }

    @Test
    void invalidCredentials3() {

        login = new Login(driver);
        login.login("Admin","");
        String actualErrorMessage = login.getErrorMessage();
        assertEquals("Password cannot be empty", actualErrorMessage);

    }

    @Test
    void invalidCredentials4() {

        login = new Login(driver);
        login.login("","");
        String actualErrorMessage = login.getErrorMessage();
        assertEquals("Username cannot be empty", actualErrorMessage);

    }


    @AfterEach
    void tearDown() {
        if (driver != null) {
            driver.close();
        }
    }
    
}

We can see that the same method is called multiple times. This is a duplication of code. The output of the above program is

Now, we will parametrize the same test. To do so, we need to add a dependency to the POM.xml.

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>org.example</groupId>
    <artifactId>JUnit5Demo</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <maven.compiler.source>11</maven.compiler.source>
        <maven.compiler.target>11</maven.compiler.target>
    </properties>

    <dependencies>

        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter-engine</artifactId>
            <version>5.8.2</version>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter-api</artifactId>
            <version>5.8.2</version>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>org.seleniumhq.selenium</groupId>
            <artifactId>selenium-java</artifactId>
            <version>3.141.59</version>
        </dependency>

        <dependency>
            <groupId>io.github.bonigarcia</groupId>
            <artifactId>webdrivermanager</artifactId>
            <version>5.1.0</version>
        </dependency>

        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter-params</artifactId>
            <version>5.8.2</version>
            <scope>test</scope>
        </dependency>

    </dependencies>

    <build>
        <plugins>

            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-surefire-plugin</artifactId>
                <version>3.0.0-M5</version>
            </plugin>

            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-surefire-plugin</artifactId>
                <version>3.0.0-M5</version>

                <dependencies>
                    <dependency>
                        <groupId>org.junit.jupiter</groupId>
                        <artifactId>junit-jupiter-engine</artifactId>
                        <version>5.8.2</version>
                    </dependency>
                </dependencies>
            </plugin>
        </plugins>
    </build>

</project>

There are multiple ways to parameterize the test. To start with:

  1. Replace @Test annotation with @ParameterizedTest annotation provided by the JUnit5 framework.
  2. Add parameters to the loginTest() method. In this example, we will add a username and a password parameter.
  3. Add the parameters source. In this example, we will use the @CsvFileSource annotation.

In this example, will retrieve the data from CSV. This CSV file is placed under src/test/resources. Below is the example of the credentials.csv file.

To know all the different types of parameterization methods, please refer to this tutorial. This tutorial will show the 2 most common ways to parameterize tests in JUnit5.

1.@CsvSource

@CsvSource allows us to express argument lists as comma-separated values (i.e., CSV String literals). Each string provided via the value attribute in @CsvSource represents a CSV record and results in one invocation of the parameterized test. An empty, quoted value (”) results in an empty String. This can be seen in the below example.

import io.github.bonigarcia.wdm.WebDriverManager;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.CsvFileSource;
import org.junit.jupiter.params.provider.CsvSource;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.chrome.ChromeOptions;
import static org.junit.jupiter.api.Assertions.assertEquals;

public class ParameterizedLoginPageTest {

    WebDriver driver;

    LoginPage loginPage;

    @BeforeEach
     void setUp() {

        WebDriverManager.chromedriver().setup();
        ChromeOptions chromeOptions = new ChromeOptions();
        driver = new ChromeDriver(chromeOptions);
        driver.manage().window().maximize();
        driver.get("https://opensource-demo.orangehrmlive.com/");

    }

    @ParameterizedTest
    @CsvSource({
            "admin123,admin123,Invalid credentials",
            "'',admin123,Username cannot be empty",
            "Admin,'',Password cannot be empty",
            "'','',Username cannot be empty"
    })

    void invalidCredentials1(String username, String password, String errorMessage) {

        loginPage = new LoginPage(driver);
        loginPage.login(username,password);
        String actualErrorMessage = loginPage.getErrorMessage();
        assertEquals(errorMessage, actualErrorMessage);

    }

    @AfterEach
     void tearDown() {
        driver.close();
    }
}

The output of the above program is

2. @CsvFileSource

@CsvFileSource lets us use comma-separated value (CSV) files from the classpath or the local file system.

We can see in the below example, that we have skipped the first line from the credentials.csv file as it is the heading of the file. invalidCredentials() method got 4 different set of the test data from CSV file using parameterization. JUnit5 ignores the headers via the numLinesToSkip attribute.

In @CsvFileSource, an empty, quoted value (“”) results in an empty String in JUnit5.

import io.github.bonigarcia.wdm.WebDriverManager;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.CsvFileSource;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.chrome.ChromeOptions;
import static org.junit.jupiter.api.Assertions.assertEquals;

public class ParameterizedLoginPageTest {

    WebDriver driver;

    LoginPage loginPage;

    @BeforeEach
     void setUp() {

        WebDriverManager.chromedriver().setup();
        ChromeOptions chromeOptions = new ChromeOptions();
        driver = new ChromeDriver(chromeOptions);
        driver.manage().window().maximize();
        driver.get("https://opensource-demo.orangehrmlive.com/");

    }

    @ParameterizedTest
    @CsvSource({
            "admin123,admin123,Invalid credentials",
            "'',admin123,Username cannot be empty",
            "Admin,'',Password cannot be empty",
            "'','',Username cannot be empty"
    })


    @ParameterizedTest
    @CsvFileSource(files = "src/test/resources/credentials.csv", numLinesToSkip = 1)
    void invalidCredentials(String username, String password, String errorMessage) {

        loginPage = new LoginPage(driver);
        loginPage.login(username,password);
        String actualErrorMessage = loginPage.getErrorMessage();
        assertEquals(errorMessage, actualErrorMessage);

    }


    @AfterEach
     void tearDown() {
        driver.close();
    }
}
     

The result of the above program is

Congratulations!! We have seen how Selenium tests are parameterized in JUnit5. Happy Learning.

How to parameterize Tests in JUnit5

HOME

JUnit5  enables us to execute a single test method multiple times with a different sets of data. This is called Parameterization. Parameterized Tests are declared just like regular @Test methods but use the @ParameterizedTest annotation.

This article shows you how to run a test multiple times with different arguments, so-called ‘Parameterized Tests’, let’s see the following ways to provide arguments to the test:

  • @ValueSource
  • @EnumSource
  • @MethodSource
  • @CsvSource
  • @CsvFileSource
  • @ArgumentsSource

We need to add junit-jupiter-params to support parameterized tests. In the case of Maven, add the dependency to POM.xml

<dependency>
    <groupId>org.junit.jupiter</groupId>
    <artifactId>junit-jupiter-params</artifactId>
    <version>5.8.2</version>
    <scope>test</scope>
</dependency>

In case of Gradle, add the dependency as

testCompile("org.junit.jupiter:junit-jupiter-params:5.8.2")

1. @ValueSource

Let us start with a simple example. The following example demonstrates a parameterized test that uses the @ValueSource annotation to specify an integer array as the source of arguments. The following @ParameterizedTest method will be invoked three times, with the values 5,6, and 0 respectively.

@ParameterizedTest
@ValueSource(ints = {5, 6, 0})
void test_int_arrays(int b) {

    int a= 5;
    int sum = a + b;
    assertTrue(sum>8);
 }

When executing the above-parameterized test method, each invocation will be reported separately.

The output of the above program is:

One of the limitations of value sources is that they only support these types:

  • short (with the shorts attribute)
  • byte (bytes attribute)
  • int (ints attribute)
  • long (longs attribute)
  • float (floats attribute)
  • double (doubles attribute)
  • char (chars attribute)
  • java.lang.String (strings attribute)
  • java.lang.Class (classes attribute)

Also, we can only pass one argument to the test method each time.

In the below example, an array of strings is passed as the argument to the Parameterized Test.

@ParameterizedTest(name = "#{index} - Run test with args={0}")
@ValueSource(strings = {"java", "python", "javascript","php"})
void test_string_arrays(String arg) {
        assertTrue(arg.length() > 1);
}

The output of the above program is:

@NullSource

It provides a single null an argument to the annotated @ParameterizedTest method.

    @ParameterizedTest()
    @NullSource
    void nullString(String text) {
        assertTrue(text == null);
    }
    

The output of the above program is:

@EmptySource

It provides a single empty argument to the annotated @ParameterizedTest method of the following types:

  • java.lang.String
  • java.util.List
  • java.util.Set
  • java.util.Map
  • primitive arrays (e.g. int[])
  • object arrays (e.g. String[])
 @ParameterizedTest
    @EmptySource
    void testMethodEmptySource(String argument) {
        assertTrue(StringUtils.isEmpty(argument));
        assertTrue(StringUtils.isBlank(argument));
    }

The output of the above program is:

@NullAndEmptySource

We can pass empty or null values into the test via @EmptySource, @NullSource, or @NullAndEmptySource (since JUnit 5.4).

Let’s see the following example to test an isEmpty() method.

import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.EmptySource;
import org.junit.jupiter.params.provider.NullSource;
import org.junit.jupiter.params.provider.ValueSource;
import static org.junit.jupiter.api.Assertions.*;

public class ParameterizedTestDemo {

    @ParameterizedTest(name = "#{index} - isEmpty()? {0}")
    @NullSource
    @EmptySource
    @ValueSource(strings = { " ", "   ", "\t", "\n","a"})
    void nullEmptyAndBlankStrings(String text) {
        assertTrue(text == null || text.trim().isEmpty());
    }
}

The parameterized test method result in seven invocations: 1 for null, 1 for the empty string, 4 for the explicit blank strings supplied via @ValueSource, and 1 non-blank string “a” supplied via @ValueSource.

The output of the above program is:

2. @EnumSource

@EnumSource provides a convenient way to use Enum constants.

public class EnumParameterizedTest {

    enum Error {
         Request_Invalid,
         Request_Timeout,
         RequestHeader_Invalid,
         Concurrency_Failed,
         ExternalCall_Failed,
         Schema_Invalid,
         Authentication_Failed;
    }

    @ParameterizedTest
    @EnumSource(Error.class)
    void test_enum(Error error) {
       assertNotNull(error);
    }
}

The output of the above program is:

The annotation provides an optional names attribute that lets you specify which constants shall be used, like in the following example. If omitted, all constants will be used.

    @ParameterizedTest(name = "#{index} - Is Error contains {0}?")
    @EnumSource(value = Error.class, names = {"Request_Invalid", "ExternalCall_Failed", "Concurrency_Failed", "Authentication_Failed"})
    void test_enum_include(Error error) {
       assertTrue(EnumSet.allOf(Error.class).contains(error));
    }

The output of the above program is:

The @EnumSource annotation also provides an optional mode attribute that enables fine-grained control over which constants are passed to the test method. For example, you can exclude names from the enum constant pool or specify regular expressions as in the following examples.

 @ParameterizedTest
 @EnumSource(value = Error.class, mode = EnumSource.Mode.EXCLUDE, names = {"Request_Invalid", "Request_Timeout", "RequestHeader_Invalid"})
    void test_enum_exclude(Error error) {
        EnumSet<Error> excludeRequestRelatedError = EnumSet.range(Error.Concurrency_Failed, Error.Authentication_Failed);
        assertTrue(excludeRequestRelatedError.contains(error));
  }

The output of the above program is:

EnumSource.Mode.EXCLUDE – It selects all declared enum constants except those supplied via the names attribute.

EnumSource.Mode.MATCH_ALL – It selects only those enum constants whose names match any pattern supplied via the names attribute.

  @ParameterizedTest
    @EnumSource(mode = EnumSource.Mode.MATCH_ALL, names = "^.*Invalid")
    void test_match(Error error) {
        assertTrue(error.name().contains("Invalid"));
    }

The output of the above program is

3. @MethodSource

@MethodSource allows you to refer to one or more factory methods of the test class or external classes.

Factory methods within the test class must be static unless the test class is annotated with @TestInstance(Lifecycle.PER_CLASS); whereas, factory methods in external classes must always be static. In addition, such factory methods must not accept any arguments.

If you only need a single parameter, you can return a Stream of instances of the parameter type as demonstrated in the following example.

   @ParameterizedTest(name = "#{index} - Test with String : {0}")
    @MethodSource("stringProvider")
    void test_method_string(String arg) {
        assertNotNull(arg);
    }

    // this need static
    static Stream<String> stringProvider() {
        return Stream.of("java", "junit5", null);
    }

The output of the above program is

If you do not explicitly provide a factory method name via @MethodSource, JUnit Jupiter will search for a factory method that has the same name as the current @ParameterizedTest method by convention. This is demonstrated in the following example.

    @ParameterizedTest(name = "#{index} - Test with String : {0}")
    @MethodSource
    void test_no_factory_methodname(String arg) {
        assertNotNull(arg);
    }

    static Stream<String> test_no_factory_methodname() {
        return Stream.of("java", "junit5", null);
    }

The output of the above program is

Streams for primitive types (DoubleStream, IntStream, and LongStream) are also supported as demonstrated by the following example.

 @ParameterizedTest(name = "#{index} - Test with Int : {0}")
    @MethodSource("rangeProvider")
    void test_method_int(int arg) {
        assertTrue(arg < 6);
    }
    
    static IntStream rangeProvider() {
        return IntStream.range(0, 6);
    }

The output of the above program is

If a parameterized test method declares multiple parameters, you need to return a collection, stream, or array of Arguments instances or object arrays as shown below.

    @ParameterizedTest
    @MethodSource("stringIntAndListProvider")
    void testWithMultiArgMethodSource(String str, int num, List<String> list) {
        assertEquals(5, str.length());
        assertTrue(num >=1 && num <=2);
        assertEquals(2, list.size());
    }

    static Stream<Arguments> stringIntAndListProvider() {
        return Stream.of(
                arguments("apple", 1, Arrays.asList("a", "b")),
                arguments("lemon", 2, Arrays.asList("x", "y"))
        );
    }

The output of the above program is

4. @CsvSource

@CsvSource allows you to express argument lists as comma-separated values (i.e., CSV String literals). Each string provided via the value attribute in @CsvSource represents a CSV record and results in one invocation of the parameterized test.

import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.CsvSource;
import static org.junit.jupiter.api.Assertions.assertEquals;

public class CSVParameterizedTest {

    @ParameterizedTest
    @CsvSource({
            "java,      4",
            "javascript,   7",
            "python,    6",
            "HTML,    4",
    })


    void test(String str, int length) {
        assertEquals(length, str.length());
    }
}

The output of the above program is

5. @CsvFileSource

@CsvFileSource lets us use comma-separated value (CSV) files from the classpath or the local file system. Each record from a CSV file results in one invocation of the parameterized test. The first record may optionally be used to supply CSV headers.

csvdemo.csv

    @ParameterizedTest
    @CsvFileSource(resources = "/csvdemo.csv")

    void testLength(String str, int length) {
        Assertions.assertEquals(length, str.length());
    }

The output of the above program is

csv file with the heading

JUnit can ignore the headers via the numLinesToSkip attribute.

    @ParameterizedTest
    @CsvFileSource(files = "src/test/resources/csvdemo.csv",numLinesToSkip = 1)

    void testStringLength(String str, int length) {
        Assertions.assertEquals(length, str.length());
    }

The output of the above program is

If you would like the headers to be used in the display names, you can set the useHeadersInDisplayName attribute to true. The examples below demonstrate the use of useHeadersInDisplayName.

 @ParameterizedTest(name = "[{index}] {arguments}")
    @CsvFileSource(files = "src/test/resources/csvdemo.csv",useHeadersInDisplayName = true)

    void testStringLength1(String str, int length) {
        assertEquals(length, str.length());
    }

The output of the above program is

6. @ArgumentsSource

@ArgumentsSource can be used to specify a custom, reusable ArgumentsProvider. Note that an implementation of ArgumentsProvider must be declared as either a top-level class or as a static nested class.

import org.junit.jupiter.api.extension.ExtensionContext;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.ArgumentsProvider;
import java.util.stream.Stream;

public class CustomArgumentsProvider implements ArgumentsProvider {

    @Override
    public Stream<? extends Arguments>
    provideArguments(ExtensionContext extensionContext) throws Exception {
        return Stream.of("java", "junit5", "junit4", null).map(Arguments::of);
    }
}
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ArgumentsSource;
import static org.junit.jupiter.api.Assertions.assertNotNull;

public class ArgumentsSourceTest {

    @ParameterizedTest
    @ArgumentsSource(CustomArgumentsProvider.class)
    void test_argument_custom(String arg) {
        assertNotNull(arg);
    }
}

The output of the above program is

Congratulation. We have understood parameterization in JUnit5 tests. Happy Learning!!

JUnit Tutorials

HOME

JUnit is an open source Unit Testing Framework for JAVA.
JUnit is a simple framework to write repeatable tests. It is an instance of the xUnit architecture for unit testing frameworks.

JUnit4

Chapter 1 How to configure Junit in Intellij
Chapter 2 How to run JUnit5 tests through Command Line
Chapter 3 JUnit4 Assertions
Chapter 4 How to Parameterize tests in JUnit4
Chapter 5 How to generate JUnit4 Report
Chapter 6 Integration of Cucumber with Selenium and JUnit
Chapter 7 Integration of Serenity with Cucumber6 and JUnit5
Chapter 8 Integration of Serenity with JUnit4
Chapter 9 Rest API Test in Cucumber BDD
Chapter 10 Difference between JUnit4 and JUnit5

JUnit5

Chapter 1 JUnit5 Assertions Example
Chapter 2 Grouped Assertions in JUnit 5 – assertAll()
Chapter 3 How to Retry Test in JUnit5 – @RepeatedTest
Chapter 4 How to disable tests in JUnit5 – @Disabled
Chapter 5 How to run JUnit5 tests in order
Chapter 6 How to tag and filter JUnit5 tests – @Tag
Chapter 7 Assumptions in JUnit5
Chapter 8 How to parameterized Tests in JUnit5
Chapter 9 How to run parameterized Selenium test using JUnit5
Chapter 10 Integration of Serenity with JUnit5
Chapter 11 Integration of Serenity with Cucumber6 and JUnit5

Gradle

Chapter 1 Gradle – Allure Report for Selenium and JUnit4
Chapter 2 Gradle Project with Cucumber, Selenium and JUnit4
Chapter 3 Gradle – Integration of Selenium and JUnit5

DataProviders in TestNG

HOME

In the last tutorial, I have explain the Parameters in TestNG which passes different test data to the test case as arguments. Similar to TestNG Parameters, DataProviders are a means to pass data to test scripts in TestNG. In this tutorial, I will explain about the DataProviders in TestNG.

What is DataProvider in TestNG?

The DataProvider in TestNG is another way to pass the parameters in the test function, the other one being TestNG parameters. Using DataProvider in TestNG, we can easily inject multiple values into the same test case. It comes inbuilt in TestNG and is popularly used in data-driven frameworks.

Syntax of DataProvider

@DataProvider (name = "name_of_dataprovider")
public Object[][] dpMethod() {
    return new Object [][] { values}
}
  •  A Data Provider is a method on the class that returns an array of array of objects.  This method is annotated with @DataProvider
  • A @Test method specifies its Data Provider with the dataProvider attribute. This name must correspond to a method on the same class annotated with @DataProvider(name=”…”) with a matching name.
  • TestNG dataprovider returns a 2d list of objects..An array of array of objects (Object[][]) where the first dimension’s size is the number of times the test method will be invoked and the second dimension size contains an array of objects that must be compatible with the parameter types of the test method.
  • DataProviders are not declared on top of the functions like TestNG parameters but have a method of their own, which in regular speaking terms called a dataprovider method. For example, dpMethod here.
  • The dataprovider name calls the dataprovider method, and if there is no name specified by the tester, then the dataprovider method is the default name used in the receiving @Test case.
  • Data providers can run in parallel with the attribute parallel.

Below is the basic example of using DataProvider in TestNG.

import org.openqa.selenium.By;
import org.openqa.selenium.Keys;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.chrome.ChromeDriver;
import org.testng.annotations.AfterMethod;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;

public class DataProviderDemo {

	WebDriver driver;

	@DataProvider(name = "testData")
	public Object[][] dataProvFunc() {
		return new Object[][] { { "Selenium" }, { "TestNG" } };
	}

	@BeforeMethod
	public void setUp() {

		System.out.println("Start the test");
		System.setProperty("webdriver.chrome.driver",
				"C:\\Users\\Vibha\\Software\\chromedriver\\chromedriver.exe");
		driver = new ChromeDriver();
		driver.get("https://www.bing.com/");
		driver.manage().window().maximize();

	}

	// Passing the dataProvider to the test method through @Test annotation
	@Test(dataProvider = "testData")
	public void search(String keyWord) {
		WebElement txtBox = driver.findElement(By.id("sb_form_q"));
		txtBox.sendKeys(keyWord);
		System.out.println("Keyword entered is : " + keyWord);
		txtBox.sendKeys(Keys.ENTER);
		System.out.println("Search result is displayed.");
	}

	@AfterMethod
	public void burnDown() {
		driver.quit();

		System.out.println("End the test");
	}

}

In the above example, I am passing two search keywords, viz “Selenium” and “TestNG” to the test method using the DataProvider method. You can run the code and check the output. It will be as shown below-

Here, Test is executed with two values, but we have run the test only once.

Inheriting DataProvider in TestNG

It is messy to have supporting methods like DataProvider and test code in one class. It is always preferred to declare the test case in one class and define TestNG parameters like DataProviders in another class. By default, the data provider will be looked for in the current test class or one of its base classes. If you want to put your data provider in a different class, it needs to be a static method or a class with a non-arg constructor, and you specify the class where it can be found in the dataProviderClass attribute.

Let us create separate classes for the DataProvider method and the test method, as shown below:

DataProvider Class

public class DPDemo {

	@DataProvider(name = "testData")
	public Object[][] dataProvFunc() {
		return new Object[][] { 
          { "Selenium" }, { "TestNG" }, { "Automation" } };
	}
}

We can see that all we did was create a DataProvider method in a Class and create a new class for Test Code.

public class DataProviderInheritanceDemo {

	WebDriver driver;

	@BeforeMethod
	public void setUp() {

		System.out.println("Start the test");
		System.setProperty("webdriver.chrome.driver",
				"C:\\Users\\Vibha\\Software\\chromedriver\\chromedriver.exe");
		driver = new ChromeDriver();
		driver.get("https://www.bing.com/");
		driver.manage().window().maximize();

	}

	// Passing the dataProvider to the test method through @Test annotation
	@Test(dataProvider = "testData", dataProviderClass = DPDemo.class)
	public void search(String keyWord) {
		WebElement txtBox = driver.findElement(By.id("sb_form_q"));
		txtBox.sendKeys(keyWord);
		System.out.println("Keyword entered is : " + keyWord);
		txtBox.sendKeys(Keys.ENTER);
		System.out.println("Search result is displayed.");
	}

	@AfterMethod
	public void burnDown() {
		driver.quit();

		System.out.println("End the test");
	}

}

As you can see, to handle the inheritance, all we did was add an attribute to the test method (highlighted above), which specifies the class that has the DataProvider method. 

Passing Multiple Parameter Values in TestNG DataProviders

Passing multiple values is pretty similar to passing numerous parameters. The only difference is that we will pass various values to a single parameter so that a string of input(s) is sent in one go.

Let us quickly understand this concept with the help of the code as shown below.

DataProvider Class

public class DPDemo {

	@DataProvider(name = "testData")
	public Object[][] dataProvFunc() {
		return new Object[][] { { "Automation Tester", "2-5 years" }, { "Performance Tester", "3+ years" },
				{ "DevOps", "5+ years" } };
	}
}

Test Code – DataProviderInheritanceDemo

public class DataProviderInheritanceDemo {

	WebDriver driver;

	@BeforeMethod
	public void setUp() {

		System.out.println("Start the test");
		System.setProperty("webdriver.chrome.driver",
				"C:\\Users\\Vibha\\Software\\chromedriver\\chromedriver.exe");
		driver = new ChromeDriver();
		driver.get("https://www.bing.com/");
		driver.manage().window().maximize();

	}

	// Passing the dataProvider to the test method through @Test annotation
	@Test(dataProvider = "testData", dataProviderClass = DPDemo.class)
	public void search(String keyWord1, String keyWord2) {

		WebElement txtBox = driver.findElement(By.id("sb_form_q"));
		txtBox.sendKeys(keyWord1, keyWord2);
		System.out.println("Keyword entered is : " + keyWord1 + " " + keyWord2);
		txtBox.sendKeys(Keys.ENTER);
		System.out.println("Search result is displayed.");
	}

	@AfterMethod
	public void burnDown() {
		driver.quit();

		System.out.println("End the test");
	}
}

Run the test script, and you will see both the values for the TestNG parameters being passed in one go, the output for it would be as follows-

We are done! Congratulations on making it through this tutorial and hope you found it useful! Happy Learning!!

How to pass Parameters in TestNG

 HOME

One of the important features of TestNG is the ability to pass different test data to a test case as arguments, which is called parametrization

There are mainly two ways through which we can provide parameter values to TestNG tests.

  1. Through testng.xml  XML configuration file
  2. Through DataProviders

In this tutorial, we will discuss using testng.xml for parametrization. If we need to pass some simple values such as String or Integer types to the test methods at runtime, there is something called @Parameter where parameter values through TestNG XML configuration files pass to test.

@Parameters("value")

Let us explain how we can use parameters. To start with, add the below dependencies to the POM.xml in the case of the Maven project.

<dependencies>
  
      <dependency>
          <groupId>org.seleniumhq.selenium</groupId>
          <artifactId>selenium-java</artifactId>
          <version>3.141.59</version>
      </dependency>
      
      <dependency>
          <groupId>io.github.bonigarcia</groupId>
          <artifactId>webdrivermanager</artifactId>
          <version>5.1.0</version>
       </dependency>

      <dependency>
           <groupId>org.testng</groupId>
           <artifactId>testng</artifactId>
           <version>7.5</version>
           <scope>test</scope>
      </dependency>

  </dependencies>

Step 1 – Create a JAVA test class, say, TestNGParameterizationDemo.java

Step 2 – Add test method parameterizedTest() to the test class. This method takes a string as an input parameter

Add the annotation @Parameters(“browser”) to this method. The parameter passes a value from testng.xml

Step 3 – Create a TestNG.xml and pass the value of the parameter in this configuration file.

import static org.testng.Assert.assertEquals;

import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
import org.testng.annotations.AfterMethod;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Parameters;
import org.testng.annotations.Test;
import io.github.bonigarcia.wdm.WebDriverManager;

public class TestNGParameterizationDemo {
	
	 WebDriver driver;
	 
     @BeforeMethod
     @Parameters("browser")
     public void parameterizedTest(String browser) {
        if (browser.equalsIgnoreCase("firefox")) {

        	 driver = WebDriverManager.firefoxdriver().create();
             System.out.println("Browser Started :" + browser);
          
        } else if (browser.equalsIgnoreCase("chrome")) {
  
        	 driver = WebDriverManager.chromedriver().create();
             System.out.println("Browser Started :" + browser);
        }
        
        driver.get("https://opensource-demo.orangehrmlive.com/");
        driver.manage().window().maximize();
    }
     
     @Test
     public void validCredentials()  {

    	 driver.findElement(By.xpath("//*[@id='txtUsername']")).sendKeys("Admin");
    	 driver.findElement(By.xpath("//*[@id='txtPassword']")).sendKeys("admin123");
    	 driver.findElement(By.xpath("//*[@id='btnLogin']")).click();
         String newPageText = driver.findElement(By.xpath("//*[@id='content']/div/div[1]/h1")).getText();
         System.out.println("newPageText :" + newPageText);
         assertEquals(newPageText,"Dashboard");
     }

     @Test
     public void invalidCredentials() {
     	
         driver.findElement(By.xpath("//*[@id='txtUsername']")).sendKeys("1234");
         driver.findElement(By.xpath("//*[@id='txtPassword']")).sendKeys("12342");
         driver.findElement(By.xpath("//*[@id='btnLogin']")).click();
         String actualErrorMessage = driver.findElement(By.xpath("//*[@id='spanMessage']")).getText();
         System.out.println("Actual ErrorMessage :" + actualErrorMessage);
         assertEquals(actualErrorMessage,"Invalid credentials");

     }

     @AfterMethod
     public  void closeBrowser() {
         driver.quit();
     }
}

TestNG.xml looks like as shown below. Here, the parameter name is the browser name value for the browser is “Chrome”. So, this “Chrome” value is passed to Test as a parameter and as a result, a Google Chrome browser opens. Similarly, the same tests are run using Firefox, as it is mentioned in the testng.xml.

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

<suite name="Suite ">
 
  <test name="Chrome Test">
   <parameter name="browser" value="chrome" />
   <classes>
      <class name="com.example.TestNGParameterizationDemo" />
   </classes>
  </test> <!-- Test -->
  
   <test name="Firefox Test">
   <parameter name="browser" value="firefox" />
   <classes>
      <class name="com.example.TestNGParameterizationDemo" />
   </classes>
  </test> <!-- Test -->
</suite> <!-- Suite -->

The output of the above program is