JUnit 5 Parameterized Tests

 

JUnit 5 provides parameterized tests, which let you run a test multiple times with different arguments. This is especially useful when you want to test a function with various inputs without writing repetitive test cases.

Basic Structure of a Parameterized Test

To create a parameterized test, use the @ParameterizedTest annotation. You also need a source of parameters, which JUnit provides through different annotations like @ValueSource, @CsvSource, @CsvFileSource.

  • ValueSource: Supplies a single array of primitive types or strings.
@Service
public class AccountValidationService {

// Example rules:
// - Account number must be 10 digits
// - Must start with "ACC"

public boolean validateAccountNumber(String accountNumber) {
if (accountNumber == null) {
return false;
}
if (!accountNumber.startsWith("ACC")) {
return false;
}
if (accountNumber.length() != 10) {
return false;
}
String digits = accountNumber.substring(3);
return digits.chars().allMatch(Character::isDigit);
}

}

@ExtendWith(MockitoExtension.class)
class AccountValidationServiceTest {

@InjectMocks
private AccountValidationService accountValidationService;

@DisplayName("Test validateAccountNumber with valid account numbers")
@ParameterizedTest(name = "Valid Account Number: {0}")
@ValueSource(strings = {"ACC1234567", "ACC0000000", "ACC9876543"})
void testValidateAccountNumber_Valid(String accountNumber) {
assertTrue(accountValidationService.validateAccountNumber(accountNumber));
}

}
  • CsvSource: Supplies multiple parameters with values separated by commas.
@Service
public class InterestCalculatorService {

public BigDecimal calculateInterest(String accountType, BigDecimal balance) {
BigDecimal rate = switch (accountType.toUpperCase()) {
case "SAVINGS" -> BigDecimal.valueOf(0.04); // 4% interest
case "CURRENT" -> BigDecimal.valueOf(0.02); // 2% interest
case "FIXED" -> BigDecimal.valueOf(0.06); // 6% interest
default -> throw new IllegalArgumentException("Invalid account type");
};

return balance.multiply(rate).setScale(2, RoundingMode.HALF_UP);
}

}

@ExtendWith(MockitoExtension.class)
class InterestCalculatorServiceTest {

@InjectMocks
private InterestCalculatorService calculatorService;

@DisplayName("Parameterized Test for calculateInterest with BigDecimal")
@ParameterizedTest(name = "{index} => accountType={0}, balance={1}, expectedInterest={2}")
@CsvSource({
"SAVINGS, 1000, 40.00",
"CURRENT, 1000, 20.00",
"FIXED, 1000, 60.00"
})

public void calculateInterestTest(String accountType, String balance, String expectedInterest) {
BigDecimal balanceBD = new BigDecimal(balance);
BigDecimal expectedInterestBD = new BigDecimal(expectedInterest);

BigDecimal calculatedInterest = calculatorService.calculateInterest(accountType, balanceBD);

assertEquals(expectedInterestBD, calculatedInterest);
}

}

CsvFileSource: Loads CSV data from a file.

@ParameterizedTest
@CsvFileSource(resources = "/test-data.csv", numLinesToSkip = 1)
void testCsvFile(String input, String expected) {
assertEquals(expected, input.toUpperCase());
}
  • EnumSource: Supplies enum constants as parameters.
enum Status { ACTIVE, INACTIVE, PENDING }

@ParameterizedTest
@EnumSource(Status.class)
void testEnum(Status status) {
assertNotNull(status);
}
  • MethodSource: Supplies arguments from a static method in the test class. This method must return a Stream of arguments.
static Stream<Arguments> provideStringsForIsBlank() {
return Stream.of(
Arguments.of(null, true),
Arguments.of("", true),
Arguments.of(" ", true),
Arguments.of("not blank", false)
);
}

@ParameterizedTest
@MethodSource("provideStringsForIsBlank")
void testIsBlank(String input, boolean expected) {
assertEquals(expected, isBlank(input));
}

boolean isBlank(String str) {
return str == null || str.trim().isEmpty();
}
 

Yorumlar

Bu blogdaki popüler yayınlar

Kafka Message Key Hashing

Transactional Outbox Pattern