Writing Parameterized Tests With JUnit 4

After we have finished this lesson, we

  • Know how we can write parameterized tests by using the test runner provided by JUnit 4.
  • Understand why we shouldn’t write parameterized tests that use the test runner provided by JUnit 4.
  • Can write parameterized tests by using the JUnitParams library.
This lesson assumes that:

What Is a Parameterized Test?

A parameterized test is a test case that is invoked by using a predefined input and expected output. These tests are typically used to test classes that have no external dependencies and that provide methods which have no side effects.

Let’s move on and take a look at the system under test.

Introduction to System Under Test

During this lesson we will write unit tests for a simple Calculator class. It has one method called sum() and this method returns the sum of two integers. The source code of the Calculator class looks as follows:

public class Calculator {

    public int sum(int first, int second) {
        return first + second;
    }
}

Next, we will write parameterized tests for the sum() method by using JUnit 4.

Writing Parameterized Tests With JUnit 4

We can write parameterized tests with JUnit 4 by following these steps:

First, we have to ensure that our unit tests are run by the Parameterized class. After we have configured the used test runner, the source code of our test class looks as follows:

import org.junit.experimental.categories.Category;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;

@RunWith(Parameterized.class)
@Category(UnitTest.class)
public class StandardCalculatorTest {

}

Second, we have to create a static method that returns a Collection of Object arrays, and annotate this method with the @Parameters annotation. This method creates the input provided to the tested object and describes the expected output.

After we have created this method, the source code of our test class looks as follows:

import org.junit.experimental.categories.Category;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameters;

import java.util.Arrays;
import java.util.Collection;

@RunWith(Parameterized.class)
@Category(UnitTest.class)
public class StandardCalculatorTest {

    @Parameters
    public static Collection<Object[]> data() {
        return Arrays.asList(new Object[][] {
                { 0, 0, 0 }, { 1, 1, 2 }
        });
    }
}

Third, we have to add the required fields into our test class. These fields contain the tested object, the input provided to the tested object, and the expected output.

After we have added these fields into our test class, its source code looks as follows:

import org.junit.Test;
import org.junit.experimental.categories.Category;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameters;

import java.util.Arrays;
import java.util.Collection;

import static org.assertj.core.api.Assertions.assertThat;

@RunWith(Parameterized.class)
@Category(UnitTest.class)
public class StandardCalculatorTest {

    @Parameters
    public static Collection<Object[]> data() {
        return Arrays.asList(new Object[][] {
                { 0, 0, 0 }, { 1, 1, 2 }
        });
    }

    private final Calculator calculator;
    private final int first;
    private final int second;
    private final int expectedSum;
}

Fourth, we have to write a constructor that takes the input and output data as constructor arguments. These arguments are provided by the custom test runner which obtains them by invoking the data() method. This constructor also creates the tested object.

After we have created this constructor, the source code of our test class looks as follows:

import org.junit.Test;
import org.junit.experimental.categories.Category;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameters;

import java.util.Arrays;
import java.util.Collection;

import static org.assertj.core.api.Assertions.assertThat;

@RunWith(Parameterized.class)
@Category(UnitTest.class)
public class StandardCalculatorTest {

    @Parameters
    public static Collection<Object[]> data() {
        return Arrays.asList(new Object[][] {
                { 0, 0, 0 }, { 1, 1, 2 }
        });
    }

    private final Calculator calculator;
    private final int first;
    private final int second;
    private final int expectedSum;

    public StandardCalculatorTest(int first, 
                                  int second, 
                                  int expectedSum) {
        
        this.calculator = new Calculator();
        this.first = first;
        this.second = second;
        this.expectedSum = expectedSum;
    }
}

Fifth, we have to write one unit test which verifies that the sum() method of the Calculator class is working as expected when we use the test data provided by the data() method.

After we have written this unit test, the source code of our test class looks as follows:

import org.junit.Test;
import org.junit.experimental.categories.Category;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameters;

import java.util.Arrays;
import java.util.Collection;

import static org.assertj.core.api.Assertions.assertThat;

@RunWith(Parameterized.class)
@Category(UnitTest.class)
public class StandardCalculatorTest {

    @Parameters
    public static Collection<Object[]> data() {
        return Arrays.asList(new Object[][] {
                { 0, 0, 0 }, { 1, 1, 2 }
        });
    }

    private final Calculator calculator;
    private final int first;
    private final int second;
    private final int expectedSum;

    public StandardCalculatorTest(int first, 
                                  int second, 
                                  int expectedSum) {
        
        this.calculator = new Calculator();
        this.first = first;
        this.second = second;
        this.expectedSum = expectedSum;
    }

    @Test
    public void shouldReturnCorrectSum() {
        int actualSum = calculator.sum(first, second);
        assertThat(actualSum).isEqualByComparingTo(expectedSum);
    }
}

When we run this test method, we notice that the shouldReturnCorrectSum() method is invoked twice by using the parameters which are configured in the data() method.

Even though this approach works, I think that we shouldn’t use it because of the following reasons:

  • It requires a lot of boilerplate code. This makes our test hard to write and read.
  • Because the test data is passed as constructor arguments, we cannot write multiple test methods which use different test data.

Luckily for us, there is a better way. Let’s find out how we can simplify our parameterized tests by using the JUnitParams library.

Writing Parameterized Tests With JUnitParams

Getting the Required Dependencies

Before we can write parameterized tests with JUnitParams, we have to declare the JUnitParams dependency in our build script.

If we are using Maven, we have to add the following snippet into our pom.xml file:

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

If we are using Gradle, we have to add the following snippet into our build.gradle file:

testCompile(
	'pl.pragmatists:JUnitParams:1.0.5'
)

Let’s move on and write the same unit test by using the JUnitParams library.

Writing the Unit Test

We can ensure that the sum() method of the Calculator class is working as expected by following these steps:

First, we have to ensure that our unit tests are run by the JUnitParamsRunner class. After we have configured the used test runner, the source code of our test class looks as follows:

import junitparams.JUnitParamsRunner;
import org.junit.experimental.categories.Category;
import org.junit.runner.RunWith;

@RunWith(JUnitParamsRunner.class)
@Category(UnitTest.class)
public class JUnitParamsCalculatorTest {

}

Second, we have to write one test method by following these steps:

  1. Add a new test method into our test class. This method takes the provided input and the expected output as method parameters.
  2. Ensure that sum() method of the Calculator class is working as expected.

After we have written this test method, the source code of our test class looks as follows:

import junitparams.JUnitParamsRunner;
import org.junit.Test;
import org.junit.experimental.categories.Category;
import org.junit.runner.RunWith;

import static org.assertj.core.api.Assertions.assertThat;

@RunWith(JUnitParamsRunner.class)
@Category(UnitTest.class)
public class JUnitParamsCalculatorTest {

    @Test
    public void shouldReturnCorrectSum(int first, 
									   int second, 
									   int expectedSum) {
        Calculator calculator = new Calculator();
        int actualSum = calculator.sum(first, second);
        assertThat(actualSum).isEqualByComparingTo(expectedSum);
    }
}

Third, we have to configure the parameters that are passed to our test method by following these steps:

  1. Annotate the test method with the @Parameters annotation.
  2. Configure the input provided to the Calculator object and the expected output.

After we have configured the parameters of our parameterized test, the source code of our test class looks as follows:

import junitparams.JUnitParamsRunner;
import junitparams.Parameters;
import org.junit.Test;
import org.junit.experimental.categories.Category;
import org.junit.runner.RunWith;

import static org.assertj.core.api.Assertions.assertThat;

@RunWith(JUnitParamsRunner.class)
@Category(UnitTest.class)
public class JUnitParamsCalculatorTest {

    @Test
    @Parameters({
            "0, 0, 0",
            "1, 1, 2"
    })
    public void shouldReturnCorrectSum(int first, 
                                       int second, 
                                       int expectedSum) {
        Calculator calculator = new Calculator();
        int actualSum = calculator.sum(first, second);
        assertThat(actualSum).isEqualByComparingTo(expectedSum);
    }
}

As we can see, JUnitParams solves the problems which we found from the parameterized tests that use the test runner provided by JUnit. It has no boilerplate code, and because the parameters are passed directly to the test method, we can have multiple test methods that use different parameters. We can even add normal test methods into our test class.

Also, one additional benefit of using JUnitParams is that we can implement custom data providers and read input data from external data sources such as CSV files.

Let’s summarize what we learned from this lesson.

Summary

This lesson has taught us four things:

  • We shouldn’t use the test runner provided by JUnit 4 because it forces us to write boilerplate code that makes our tests hard to write and read.
  • If we want to use the JUnitParams library, we have to declare the JUnitParams dependency in our build script.
  • The JUnitParams library solves the problems caused by JUnit 4.
  • The JUnitParams library supports custom data provider classes and external data sources such as CSV files.

Get the source code from Github