We should always run the test case with different values for edge testing, 1 way is to create multiple test cases of a method for a different set of input values, but it will have a lot of boilerplate code and is not a best practice or use JUnit 5 @ParameterizedTest
annotation. @Test method will invoke multiple times with different parameter values each time.
@ParameterizedTest
is just to denote that parameters of this test case will be passed at run time, but to declare from where to get the input from @ParameterizedTest
required any of the below input sources.
@ValueSource
@Arguments
@ArgumentsProvider
@ArgumentsSource
@CsvFileSource
@CsvSource
@EnumSource
@MethodSource
@Value Source
@ValueSource
provides the input as arguments to the annotated on the method. limitation of @ValueSource
is that it can provide java primitive type only.
package com.example.junit5.sujan;
import java.util.Arrays;
import java.util.List;
public class AppleCalculator {
List<String> apples = Arrays.asList("McIntosh", "Fuji", "Gala", "Jonagold", "GrannySmith", "PinkLady");
public boolean isApple(String appleName) {
return apples.contains(appleName);
}
}
package com.example.junit5.sujan;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
import static org.junit.jupiter.api.Assertions.assertTrue;
class AppleCalculatorTest {
@ParameterizedTest
@ValueSource(strings = {"McIntosh", "Mango", "Fuji", "Orange", "Gala"})
void isAppleTest(String appleName) {
AppleCalculator appleCalculator = new AppleCalculator();
assertTrue(appleCalculator.isApple(appleName), "yes its an apple");
}
}
plugins {
id 'java'
}
group 'org.example'
version '1.0-SNAPSHOT'
repositories {
mavenCentral()
}
dependencies {
testImplementation('org.junit.jupiter:junit-jupiter:5.6.2')
}
Each iteration of @ParameterizedTest
follows the JUnit 5 test case life cycle.
package com.example.junit5.sujan;
import java.util.Arrays;
import java.util.List;
public class AppleCalculator {
List<String> apples = Arrays.asList("McIntosh", "Fuji", "Gala", "Jonagold", "GrannySmith", "PinkLady");
public boolean isApple(String appleName) {
return apples.contains(appleName);
}
}
package com.example.junit5.sujan;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
import static org.junit.jupiter.api.Assertions.assertTrue;
class AppleCalculatorTest {
@ParameterizedTest
@ValueSource(strings = {"McIntosh", "Mango", "Fuji", "Orange", "Gala"})
void isAppleTest(String appleName) {
AppleCalculator appleCalculator = new AppleCalculator();
assertTrue(appleCalculator.isApple(appleName), "yes its an apple");
}
@BeforeEach
void setUp() {
System.out.println("AppleCalculatorTest.setUp");
}
@AfterEach
void tearDown() {
System.out.println("AppleCalculatorTest.tearDown");
}
}
plugins {
id 'java'
}
group 'org.example'
version '1.0-SNAPSHOT'
repositories {
mavenCentral()
}
dependencies {
testImplementation('org.junit.jupiter:junit-jupiter:5.6.2')
}
@Method Source
@ValueSource
is a great way to test different inputs for the same test case, but all the inputs need to be passed as parameters. This will not be fruitful if needs to test thousands and thousands of input or if the input is being fetched from some other source like property file or database.
To achieve this, JUnit 5 provides us @MethodSource
annotation, it takes a parameter as the name of another method that passes the stream of inputs for the test case. This method must be static and return Type must be a stream.
package com.example.junit5.sujan;
import java.util.Arrays;
import java.util.List;
public class AppleCalculator {
List<String> apples = Arrays.asList("McIntosh", "Fuji", "Gala", "Jonagold", "GrannySmith", "PinkLady");
public boolean isApple(String appleName) {
return apples.contains(appleName);
}
}
package com.example.junit5.sujan;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.MethodSource;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Stream;
import static org.junit.jupiter.api.Assertions.assertTrue;
class AppleCalculatorTest {
static Stream getAppleListFromDb() {
List appleListFromDb = Arrays.asList("McIntosh", "Mango", "Fuji", "Orange", "Gala");
return appleListFromDb.stream();
}
@ParameterizedTest
@MethodSource("getAppleListFromDb")
void isAppleTest(String appleName) {
System.out.println("appleName = " + appleName);
AppleCalculator appleCalculator = new AppleCalculator();
assertTrue(appleCalculator.isApple(appleName), "yes its an apple");
}
}
plugins {
id 'java'
}
group 'org.example'
version '1.0-SNAPSHOT'
repositories {
mavenCentral()
}
dependencies {
testImplementation('org.junit.jupiter:junit-jupiter:5.6.2')
}
@EnumSource
An @EnumSource is the way to provide input to the test case one at a time from enum constants.
package com.example.junit5.sujan;
import java.util.Arrays;
import java.util.List;
public class AppleCalculator {
List<String> apples = Arrays.asList("McIntosh", "Fuji", "Gala", "Jonagold", "GrannySmith", "PinkLady");
public boolean isApple(AppleEnum appleName) {
return apples.contains(appleName.name());
}
}
package com.example.junit5.sujan;
public enum AppleEnum {
McIntosh, Mango, Fuji, Orange, Gala
}
package com.example.junit5.sujan;
import java.util.Arrays;
import java.util.List;
public class AppleCalculator {
List<String> apples = Arrays.asList("McIntosh", "Fuji", "Gala", "Jonagold", "GrannySmith", "PinkLady");
public boolean isApple(AppleEnum appleName) {
return apples.contains(appleName.name());
}
}
if we dont provide any value as a paramter in @EnumSource
annotation then it will automatically pick up the matching Enum.
package com.example.junit5.sujan;
import java.util.Arrays;
import java.util.List;
public class AppleCalculator {
List<String> apples = Arrays.asList("McIntosh", "Fuji", "Gala", "Jonagold", "GrannySmith", "PinkLady");
public boolean isApple(AppleEnum appleName) {
return apples.contains(appleName.name());
}
}
package com.example.junit5.sujan;
public enum AppleEnum {
McIntosh, Fuji, Gala
}
package com.example.junit5.sujan;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.EnumSource;
import static org.junit.jupiter.api.Assertions.assertTrue;
class AppleCalculatorTest {
@ParameterizedTest
@EnumSource
void isAppleTest(AppleEnum appleName) {
AppleCalculator appleCalculator = new AppleCalculator();
assertTrue(appleCalculator.isApple(appleName), "yes its an apple");
}
}
We are passing enum constants in @EnumSource
as a parameter, but we wish to pass few of the constants only, not entire Enum, we can restrict this via names
parameter.
package com.example.junit5.sujan;
import java.util.Arrays;
import java.util.List;
public class AppleCalculator {
List<String> apples = Arrays.asList("McIntosh", "Fuji", "Gala", "Jonagold", "GrannySmith", "PinkLady");
public boolean isApple(AppleEnum appleName) {
return apples.contains(appleName.name());
}
}
package com.example.junit5.sujan;
public enum AppleEnum {
McIntosh, Mango, Fuji, Orange, Gala
}
package com.example.junit5.sujan;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.EnumSource;
import static org.junit.jupiter.api.Assertions.assertTrue;
class AppleCalculatorTest {
@ParameterizedTest
@EnumSource(value = AppleEnum.class, names = {"McIntosh", "Fuji", "Gala"})
void isAppleTest(AppleEnum appleName) {
AppleCalculator appleCalculator = new AppleCalculator();
assertTrue(appleCalculator.isApple(appleName), "yes its an apple");
}
}
plugins {
id 'java'
}
group 'org.example'
version '1.0-SNAPSHOT'
repositories {
mavenCentral()
}
dependencies {
testImplementation('org.junit.jupiter:junit-jupiter:5.6.2')
}
test {
useJUnitPlatform()
}
JUnit @EnumSource
annotation provides us a way to manually choose some constants out of all to pass as input in names but what if we have a lot of parameters to test except 1 or 2, then writing all names in names parameter would be boilerplate code, to handle this, Junit 5 provides us EnumSource.Mode.EXCLUDE
enum.
package com.example.junit5.sujan;
import java.util.Arrays;
import java.util.List;
public class AppleCalculator {
List<String> apples = Arrays.asList("McIntoshApple", "FujiApple", "GalaApple", "JonagoldApple", "GrannySmithApple", "PinkLadyApple");
public boolean isApple(AppleEnum appleName) {
return apples.contains(appleName.name());
}
}
package com.example.junit5.sujan;
public enum AppleEnum {
McIntoshApple, Mango, FujiApple, Orange, GalaApple
}
package com.example.junit5.sujan;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.EnumSource;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.params.provider.EnumSource.Mode.EXCLUDE;
import static org.junit.jupiter.params.provider.EnumSource.Mode.INCLUDE;
class AppleCalculatorTest {
@ParameterizedTest
@EnumSource(value = AppleEnum.class, names = {"McIntoshApple", "FujiApple", "GalaApple"}, mode = INCLUDE)
void isAppleIncludeTest(AppleEnum appleName) {
AppleCalculator appleCalculator = new AppleCalculator();
assertTrue(appleCalculator.isApple(appleName), "yes its an apple");
}
@ParameterizedTest
@EnumSource(value = AppleEnum.class, names = {"Mango", "Orange"}, mode = EXCLUDE)
void isAppleExcludeTest(AppleEnum appleName) {
AppleCalculator appleCalculator = new AppleCalculator();
assertTrue(appleCalculator.isApple(appleName), "yes its an apple");
}
}
plugins {
id 'java'
}
group 'org.example'
version '1.0-SNAPSHOT'
repositories {
mavenCentral()
}
dependencies {
testImplementation('org.junit.jupiter:junit-jupiter:5.6.2')
}
test {
useJUnitPlatform()
}