Table of contents
1.
Introduction
2.
Dependencies
2.1.
build.gradle
3.
First Impression of Parameterized Test
3.1.
Program
3.2.
Test Program
3.3.
Output
4.
Argument Sources
4.1.
Simple Values
4.1.1.
Program
4.1.2.
Test Program
4.1.3.
Output
4.2.
Null and Empty Values
4.2.1.
NullSource Annotation
4.2.2.
EmptySource Annotation
4.2.3.
NullAndEmpty Score Annotation
4.2.4.
NullEmptyAndValue Source Annotation
4.3.
Enum
4.3.1.
EnumSource Annotation
4.4.
CSV Literal
4.4.1.
CSVSource
4.5.
CSV Files
4.6.
Methods
4.6.1.
MethodSource Annotation
4.7.
Custom Argument Provider
4.7.1.
ArgumentsProvider Annotations
5.
Argument Conversion
5.1.
Explicit Conversion
6.
Argument Accessor
6.1.
Program
6.2.
Test Program
6.3.
Output
7.
Argument Aggregator
7.1.
Aggregate With Annotation
8.
FAQs
9.
Key Takeaways
Last Updated: Mar 27, 2024

JUnit Parameterized Test

Author Aman Thakur
0 upvote
Career growth poll
Do you think IIT Guwahati certified course can help you in your career?

Introduction

The term "parameterized test" refers to running the same test with different variables each time. It allows developers to save time when running the same test with just minor differences in inputs and expected outcomes.

JUnit 5, the fifth iteration of JUnit, adds plenty of new features to make developer tests easier. Parameterized tests are one such feature. This feature allows us to repeat a test procedure with various parameters numerous times. Let's get started with a detailed look into parameterized tests in this article.

Dependencies

build.gradle

The highlight text are the modifications that are to be included in the build.gradle file.

plugins {
   id 'java'
}

group 'org.example'
version '1.0-SNAPSHOT'

sourceCompatibility = 1.8

repositories {
   mavenCentral()
}

dependencies {
   testImplementation group: 'org.JUnit.jupiter', name: 'JUnit-jupiter', version: '5.8.2'
}

test {
   useJUnitPlatform()
   testLogging {
       events "passed", "skipped", "failed"
   }
}

First Impression of Parameterized Test

Parameterized tests are similar to regular tests, except they have the @ParameterizedTest annotation added to them:

Program

// create src/main/java/Numbers
public class Numbers {
   public static boolean isOdd(int number) {
       return number % 2 != 0;
   }
}
You can also try this code with Online Java Compiler
Run Code

Test Program

// create src/test/NumbersTest
import org.JUnit.jupiter.params.ParameterizedTest;
import org.JUnit.jupiter.params.provider.ValueSource;

import static org.JUnit.jupiter.api.Assertions.*;

class NumbersTest {
   @ParameterizedTest
   @ValueSource(ints = {1, 3, 5, -3, 15, Integer.MAX_VALUE})
   void isOddTest(int number){
       assertTrue(Numbers.isOdd(number));
   }
}
You can also try this code with Online Java Compiler
Run Code

Output

Argument Sources

A parameterized test, as we all know, runs the same test numerous times with various parameters.

And, perhaps, we'll be able to accomplish more than numbers, so let's get started.

Simple Values

We may supply an array of literal values to the test function using the @ValueSource annotation.

Program

// create src/main/java/Strings
public class Strings {
   public static boolean isBlank(String input) {
       return input == null || input.trim().isEmpty();
   }
}
You can also try this code with Online Java Compiler
Run Code

Test Program

// create src/test/StringsTest
class StringsTest {
   @ParameterizedTest
   @ValueSource(strings = {"", "  "})
   void isBlankStrings(String input) {
       assertTrue(Strings.isBlank(input));
   }
}
You can also try this code with Online Java Compiler
Run Code

Output

Before we continue, it's important to remember that we didn't use null as an argument. Another restriction is that null cannot be sent through a @ValueSource, even for String and Class.

Null and Empty Values

NullSource Annotation

Test Program

// create src/test/java/StringsTest
class StringsTest {
   @ParameterizedTest
   @NullSource
   void isBlankShouldReturnTrueForNullInputs(String input) {
       assertTrue(Strings.isBlank(input));
   }
}
You can also try this code with Online Java Compiler
Run Code

Output

EmptySource Annotation

Test Program

// create src/test/java/StringsTest
class StringsTest {
   @ParameterizedTest
   @EmptySource
   void isBlank_ShouldReturnTrueForEmptyStrings(String input) {
       assertTrue(Strings.isBlank(input));
   }
}
You can also try this code with Online Java Compiler
Run Code

Output

NullAndEmpty Score Annotation

Test Program

// create src/test/java/StringsTest
class StringsTest {
   @ParameterizedTest
   @NullAndEmptySource
   void isBlankShouldReturnTrueForNullAndEmptyStrings(String input) {
       assertTrue(Strings.isBlank(input));
   }
}
You can also try this code with Online Java Compiler
Run Code

Output

NullEmptyAndValue Source Annotation

Test Program

// create src/test/java/StringsTest
class StringsTest {
   @ParameterizedTest
   @NullAndEmptySource
   @ValueSource(strings = {"  ", "\t", "\n"})
   void isBlankShouldReturnTrueForAllTypesOfBlankStrings(String input) {
       assertTrue(Strings.isBlank(input));
   }
}
You can also try this code with Online Java Compiler
Run Code

Output

Enum

EnumSource Annotation

Program

// create src/main/java/Month
public enum Month {
   JANUARY, FEBRUARY, MARCH, APRIL, MAY, JUNE, JULY, AUGUST, SEPTEMBER, OCTOBER, NOVEMBER, DECEMBER;


   public int getValue() {
       return this.ordinal() + 1;
   }
}
You can also try this code with Online Java Compiler
Run Code

Test Program

// create src/test/java/EnumsTest
public class EnumsTest {
   @ParameterizedTest
   @EnumSource(Month.class) // passing all 12 months
   void getValueForAMonth_IsAlwaysBetweenOneAndTwelve(Month month) {
       int monthNumber = month.getValue();
       assertTrue(monthNumber >= 1 && monthNumber <= 12);
   }


   @ParameterizedTest
   @EnumSource(value = Month.class, names = ".+BER", mode = EnumSource.Mode.MATCH_ANY)
   void fourMonths_AreEndingWithBer(Month month) {
       EnumSet<Month> months =
               EnumSet.of(Month.SEPTEMBER, Month.OCTOBER, Month.NOVEMBER, Month.DECEMBER);
       assertTrue(months.contains(month));
   }
}
You can also try this code with Online Java Compiler
Run Code

Output

CSV Literal

Assume we want to verify that the String's toUpperCase() function returns the expected uppercase result. @ValueSource will not suffice.

For such circumstances, we must construct a parameterized test.

  1. To the test method, pass an input value and an anticipated value. 
  2. Calculate the final result using the input values.
  3. Compare and contrast the actual and predicted values.

As a result, we require argument sources that can pass numerous arguments. One of these sources is the @CSVSource:

CSVSource

@ParameterizedTest
@CsvSource({"test,TEST", "tEst,TEST", "Java,JAVA"})
void toUpperCase_ShouldGenerateTheExpectedUppercaseValue(String input, String expected) {
   String actualValue = input.toUpperCase();
   assertEquals(expected, actualValue);
}
You can also try this code with Online Java Compiler
Run Code

CSV Files

CSVFileSource Annotation
@ParameterizedTest
@CsvFileSource(resources = "/data.csv", numLinesToSkip = 1)
void toUpperCase_ShouldGenerateTheExpectedUppercaseValueCSVFile(
       String input, String expected) {
   String actualValue = input.toUpperCase();
   assertEquals(expected, actualValue);
}
You can also try this code with Online Java Compiler
Run Code

When reading CSV files, the numLinesToSkip property specifies the number of lines to skip.

@CsvFileSource does not skip any lines by default, however this functionality is sometimes beneficial for bypassing the header lines, like we did above.

The delimiter, like the plain @CsvSource, may be customised using the delimiter property.

We also have the following capabilities in addition to the column separator:

  1. The lineSeparator parameter can be used to change the line separator; the default value is newline.
  2. The encoding property can be used to change the file encoding; UTF-8 is the default.

Methods

MethodSource Annotation

@ParameterizedTest
@MethodSource("provideStringsForIsBlank")
void isBlank_ShouldReturnTrueForNullOrBlankStrings(String input, boolean expected) {
   assertEquals(expected, Strings.isBlank(input));
}
You can also try this code with Online Java Compiler
Run Code

Custom Argument Provider

ArgumentsProvider Annotations

Another sophisticated technique for passing test parameters is to create a custom implementation of the ArgumentsProvider interface:

class BlankStringsArgumentsProvider implements ArgumentsProvider {
   @Override
   public Stream<? extends Arguments> provideArguments(ExtensionContext context) {
       return Stream.of(
               Arguments.of((String) null),
               Arguments.of(""),
               Arguments.of("   ")
       );
   }
}
You can also try this code with Online Java Compiler
Run Code

Then we can use the @ArgumentsSource annotation to use this custom provider in our test.

@ParameterizedTest
@ArgumentsSource(BlankStringsArgumentsProvider.class)
void isBlank_ShouldReturnTrueForNullOrBlankStringsArgProvider(String input) {
   assertTrue(Strings.isBlank(input));
}
You can also try this code with Online Java Compiler
Run Code

Argument Conversion

Implicit Conversion
@ParameterizedTest
@CsvSource({"APRIL", "JUNE", "SEPTEMBER", "NOVEMBER"}) // Passing strings
void someMonths_Are30DaysLongCsv(Month month) {
   final boolean isALeapYear = false;
   assertEquals(30, month.length(isALeapYear));
}
You can also try this code with Online Java Compiler
Run Code

Explicit Conversion

class SlashyDateConverter implements ArgumentConverter {


   @Override
   public Object convert(Object source, ParameterContext context)
           throws ArgumentConversionException {
       if (!(source instanceof String)) {
           throw new IllegalArgumentException(
                   "The argument should be a string: " + source);
       }
       try {
           String[] parts = ((String) source).split("/");
           int year = Integer.parseInt(parts[0]);
           int month = Integer.parseInt(parts[1]);
           int day = Integer.parseInt(parts[2]);


           return LocalDate.of(year, month, day);
       } catch (Exception e) {
           throw new IllegalArgumentException("Failed to convert", e);
       }
   }
}


@ParameterizedTest
@CsvSource({"2020/12/25,2020", "2022/02/11,2022"})
void getYearAsExpected(
       @ConvertWith(SlashyDateConverter.class) LocalDate date, int expected) {
   assertEquals(expected, date.getYear());
}
You can also try this code with Online Java Compiler
Run Code

Argument Accessor

Each argument to a parameterized test corresponds to a single method parameter by default.As a result, supplying a few arguments via an argument source causes the test method signature to become excessively lengthy and complicated.

Encapsulating all provided arguments in an instance of ArgumentsAccessor and retrieving arguments by index and type is one solution to this problem.

Program

// create src/main/java/Person
public class Person {
   String firstName;
   String middleName;
   String lastName;


   // constructor
   public Person(String firstName, String middleName, String lastName) {
       this.firstName = firstName;
       this.middleName = middleName;
       this.lastName = lastName;
   }

   public String fullName() {
       if (middleName == null || middleName.trim().isEmpty()) {
           return String.format("%s %s", firstName, lastName);
       }
       return String.format("%s %s %s", firstName, middleName, lastName);
   }
}
You can also try this code with Online Java Compiler
Run Code

Test Program

// create src/test/java/PersonTest
class PersonTest {
   @ParameterizedTest
   @CsvSource({"Isaac,,Newton,Isaac Newton", "Charles,Robert,Darwin,Charles Robert Darwin"})
   void fullName_ShouldGenerateTheExpectedFullName(ArgumentsAccessor argumentsAccessor) {
       String firstName = argumentsAccessor.getString(0);
       String middleName = (String) argumentsAccessor.get(1);
       String lastName = argumentsAccessor.get(2, String.class);
       String expectedFullName = argumentsAccessor.getString(3);


       Person person = new Person(firstName, middleName, lastName);
       assertEquals(expectedFullName, person.fullName());
   }
}
You can also try this code with Online Java Compiler
Run Code

Output

Argument Aggregator

The test code may become less understandable or reusable if the ArgumentsAccessor abstraction is used directly. We can construct a bespoke and reusable aggregator to handle these difficulties.

To do this, we use the ArgumentsAggregator interface: 

class PersonAggregator implements ArgumentsAggregator {


   @Override
   public Object aggregateArguments(ArgumentsAccessor accessor, ParameterContext context)
           throws ArgumentsAggregationException {
       return new Person(
               accessor.getString(1), accessor.getString(2), accessor.getString(3));
   }
}
You can also try this code with Online Java Compiler
Run Code

Aggregate With Annotation

@ParameterizedTest
@CsvSource({"Isaac Newton,Isaac,,Newton", "Charles Robert Darwin,Charles,Robert,Darwin"})
void fullName_ShouldGenerateTheExpectedFullName(
       String expectedFullName,
       @AggregateWith(PersonAggregator.class) Person person) {


   assertEquals(expectedFullName, person.fullName());
}
You can also try this code with Online Java Compiler
Run Code

FAQs

  1. What is parameterization in performance testing?
    The capacity of the tester to adjust numerous parameters in a load test on a just-in-time basis is referred to as parameterization. A test where each virtual user signs in to the tested application with a separate UserID and Password is the simplest example.
     
  2. What are parameterized queries?
     A parameterized query is one in which the parameter values are given at execution time and placeholders are used for parameters. The primary rationale for using parameterized queries is to prevent SQL injection attacks.
     
  3. Why is parameterization necessary in performance testing?
    The approach of parameterization allows us to run a test plan several times with different sets of data. This aids in the development of a load test script that closely resembles real-world scenarios in which various users interact with different test data.
     
  4. Can we rerun failed test cases in JUnit?
    TestRule is a class in the JUnit test framework that allows you to retry failed tests. This class will restart failed tests without disrupting your test flow. Let's look at an example of a JUnit Rule in action. It gives us a great tool for writing any test that we wish to run several times.

Key Takeaways

We looked at the nuts and bolts of parameterized tests in JUnit 5 in this post. Parameterized tests vary from regular tests in two ways: they're marked with the @ParameterizedTest annotation, and they need a source for their specified parameters. By now, we should be aware that JUnit has features for converting parameters to custom target types and customising test names.

Check out JUnit Interview Questions here.

I'm sure you'll enjoy this article, and also be interested in JUnit Nested Test.As a result, never give up on your quest for knowledge.

Happy Learning!

Live masterclass