In this section, we will explore how to create custom parameterized tests in JUnit. Parameterized tests allow you to run the same test multiple times with different inputs, which is particularly useful for testing edge cases and ensuring your code works correctly with a variety of data.

Key Concepts

  1. Parameterized Tests: Tests that run multiple times with different sets of parameters.
  2. Custom Parameter Providers: Classes or methods that supply custom parameters to the tests.
  3. Annotations: Special markers in the code that indicate how the test should be parameterized.

Creating Custom Parameterized Tests

Step-by-Step Guide

  1. Define the Test Class: Create a test class that will contain the parameterized test methods.
  2. Use @ParameterizedTest Annotation: Annotate the test method with @ParameterizedTest.
  3. Provide Custom Parameters: Use @MethodSource or @ArgumentsSource to supply custom parameters.
  4. Write the Test Method: Implement the test logic that will run with each set of parameters.

Example: Custom Parameterized Test with @MethodSource

Let's create a custom parameterized test that checks if a number is even.

Step 1: Define the Test Class

import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.MethodSource;
import java.util.stream.Stream;

public class CustomParameterizedTest {

    // Step 2: Provide Custom Parameters
    static Stream<Integer> numberProvider() {
        return Stream.of(2, 4, 6, 8, 10);
    }

    // Step 3: Use @ParameterizedTest Annotation
    @ParameterizedTest
    @MethodSource("numberProvider")
    void testIsEven(int number) {
        // Step 4: Write the Test Method
        assertTrue(isEven(number));
    }

    // Helper method to check if a number is even
    boolean isEven(int number) {
        return number % 2 == 0;
    }
}

Example: Custom Parameterized Test with @ArgumentsSource

For more complex scenarios, you can create a custom ArgumentsProvider.

Step 1: Define the Test Class

import org.junit.jupiter.api.extension.ExtensionContext;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.ArgumentsProvider;
import org.junit.jupiter.params.provider.ArgumentsSource;

import java.util.stream.Stream;

public class CustomParameterizedTest {

    // Custom ArgumentsProvider
    static class CustomArgumentsProvider implements ArgumentsProvider {
        @Override
        public Stream<? extends Arguments> provideArguments(ExtensionContext context) {
            return Stream.of(
                Arguments.of(2, true),
                Arguments.of(3, false),
                Arguments.of(4, true),
                Arguments.of(5, false)
            );
        }
    }

    // Step 3: Use @ParameterizedTest Annotation
    @ParameterizedTest
    @ArgumentsSource(CustomArgumentsProvider.class)
    void testIsEven(int number, boolean expected) {
        // Step 4: Write the Test Method
        assertEquals(expected, isEven(number));
    }

    // Helper method to check if a number is even
    boolean isEven(int number) {
        return number % 2 == 0;
    }
}

Practical Exercises

Exercise 1: Custom Parameterized Test with @MethodSource

Task: Create a parameterized test that checks if a string is a palindrome.

Solution:

import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.MethodSource;
import java.util.stream.Stream;

public class PalindromeTest {

    static Stream<String> palindromeProvider() {
        return Stream.of("madam", "racecar", "level", "hello", "world");
    }

    @ParameterizedTest
    @MethodSource("palindromeProvider")
    void testIsPalindrome(String word) {
        assertEquals(isPalindrome(word), word.equals(new StringBuilder(word).reverse().toString()));
    }

    boolean isPalindrome(String word) {
        return word.equals(new StringBuilder(word).reverse().toString());
    }
}

Exercise 2: Custom Parameterized Test with @ArgumentsSource

Task: Create a parameterized test that checks if a number is prime.

Solution:

import org.junit.jupiter.api.extension.ExtensionContext;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.ArgumentsProvider;
import org.junit.jupiter.params.provider.ArgumentsSource;

import java.util.stream.Stream;

public class PrimeTest {

    static class PrimeArgumentsProvider implements ArgumentsProvider {
        @Override
        public Stream<? extends Arguments> provideArguments(ExtensionContext context) {
            return Stream.of(
                Arguments.of(2, true),
                Arguments.of(3, true),
                Arguments.of(4, false),
                Arguments.of(5, true),
                Arguments.of(6, false)
            );
        }
    }

    @ParameterizedTest
    @ArgumentsSource(PrimeArgumentsProvider.class)
    void testIsPrime(int number, boolean expected) {
        assertEquals(expected, isPrime(number));
    }

    boolean isPrime(int number) {
        if (number <= 1) return false;
        for (int i = 2; i < number; i++) {
            if (number % i == 0) return false;
        }
        return true;
    }
}

Common Mistakes and Tips

  • Mistake: Forgetting to annotate the test method with @ParameterizedTest.
    • Tip: Always double-check that your test method is properly annotated.
  • Mistake: Providing an incorrect method name in @MethodSource.
    • Tip: Ensure the method name in @MethodSource matches the actual method name that provides the parameters.
  • Mistake: Not handling edge cases in custom parameter providers.
    • Tip: Include a variety of test cases, including edge cases, to ensure comprehensive testing.

Conclusion

In this section, we learned how to create custom parameterized tests in JUnit using @MethodSource and @ArgumentsSource. We explored practical examples and exercises to reinforce the concepts. Custom parameterized tests are a powerful tool for ensuring your code works correctly with a variety of inputs, making your tests more robust and comprehensive.

Next, we will move on to Module 5: Test Suites, where we will learn how to group and run multiple tests together.

© Copyright 2024. All rights reserved