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
- Parameterized Tests: Tests that run multiple times with different sets of parameters.
- Custom Parameter Providers: Classes or methods that supply custom parameters to the tests.
- Annotations: Special markers in the code that indicate how the test should be parameterized.
Creating Custom Parameterized Tests
Step-by-Step Guide
- Define the Test Class: Create a test class that will contain the parameterized test methods.
- Use @ParameterizedTest Annotation: Annotate the test method with
@ParameterizedTest
. - Provide Custom Parameters: Use
@MethodSource
or@ArgumentsSource
to supply custom parameters. - 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.
- Tip: Ensure the method name in
- 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.
JUnit Course
Module 1: Introduction to JUnit
Module 2: Basic JUnit Annotations
- Understanding @Test
- Using @Before and @After
- Using @BeforeClass and @AfterClass
- Ignoring Tests with @Ignore
Module 3: Assertions in JUnit
Module 4: Parameterized Tests
- Introduction to Parameterized Tests
- Creating Parameterized Tests
- Using @ParameterizedTest
- Custom Parameterized Tests
Module 5: Test Suites
Module 6: Mocking with JUnit
Module 7: Advanced JUnit Features
Module 8: Best Practices and Tips
- Writing Effective Tests
- Organizing Test Code
- Test-Driven Development (TDD)
- Continuous Integration with JUnit