0
0
JUnittesting~15 mins

@NullAndEmptySource in JUnit - Deep Dive

Choose your learning style9 modes available
Overview - @NullAndEmptySource
What is it?
@NullAndEmptySource is a special annotation in JUnit 5 used in parameterized tests. It automatically provides two test inputs: one null value and one empty value (like an empty string or empty collection). This helps test how code handles cases where inputs are missing or empty without writing extra code. It simplifies writing tests that check for null and empty inputs together.
Why it matters
Handling null and empty inputs is a common source of bugs in software. Without @NullAndEmptySource, developers might forget to test these edge cases or write repetitive code to do so. This annotation ensures these important cases are always tested, improving software reliability and preventing crashes or unexpected behavior in real use.
Where it fits
Before using @NullAndEmptySource, learners should understand basic JUnit 5 parameterized tests and annotations like @ValueSource. After mastering it, learners can explore more complex parameterized test sources like @CsvSource and custom argument providers to handle diverse test data.
Mental Model
Core Idea
@NullAndEmptySource automatically feeds null and empty values into parameterized tests to catch edge cases without extra code.
Think of it like...
It's like a safety net in a trapeze act that always catches the performer if they fall, ensuring no dangerous misses go untested.
Parameterized Test Input Sources
┌───────────────────────────────┐
│       @ValueSource             │
│  (normal test values)          │
├───────────────────────────────┤
│       @NullSource              │
│  (adds null input)             │
├───────────────────────────────┤
│     @EmptySource               │
│  (adds empty input)            │
├───────────────────────────────┤
│    @NullAndEmptySource         │
│  (adds null + empty inputs)   │
└───────────────────────────────┘
Build-Up - 6 Steps
1
FoundationBasics of Parameterized Tests
🤔
Concept: Learn what parameterized tests are and why they help run the same test with different inputs.
Parameterized tests in JUnit let you run one test method multiple times with different inputs. This avoids repeating similar test code. You use annotations like @ParameterizedTest and @ValueSource to supply inputs.
Result
You can test multiple input values easily and see which inputs pass or fail.
Understanding parameterized tests is key because @NullAndEmptySource builds on this to supply special inputs automatically.
2
FoundationTesting Null and Empty Inputs
🤔
Concept: Recognize why null and empty inputs need special testing.
Null means no value, and empty means a value with no content (like "" or empty list). Many bugs happen when code doesn't handle these properly. Writing separate tests for these cases is common but repetitive.
Result
You realize that null and empty inputs are common edge cases that must be tested to avoid crashes or wrong results.
Knowing these edge cases exist motivates using tools like @NullAndEmptySource to automate their testing.
3
IntermediateUsing @NullSource and @EmptySource Separately
🤔Before reading on: do you think @NullSource and @EmptySource can be combined automatically? Commit to yes or no.
Concept: Learn how @NullSource and @EmptySource provide null and empty inputs separately in parameterized tests.
JUnit 5 offers @NullSource to add a null input and @EmptySource to add an empty input to parameterized tests. You can add them individually to test those cases. For example: @ParameterizedTest @NullSource void testWithNull(String input) { ... } @ParameterizedTest @EmptySource void testWithEmpty(String input) { ... }
Result
Tests run once with null input or once with empty input depending on the annotation used.
Understanding these separate annotations shows the need for a combined annotation to reduce repetition.
4
IntermediateCombining Null and Empty with @NullAndEmptySource
🤔Before reading on: do you think @NullAndEmptySource replaces both @NullSource and @EmptySource? Commit to yes or no.
Concept: @NullAndEmptySource combines null and empty inputs in one annotation for parameterized tests.
Instead of adding @NullSource and @EmptySource together, you can use @NullAndEmptySource alone. It automatically adds both null and empty values as inputs. Example: @ParameterizedTest @NullAndEmptySource void testWithNullAndEmpty(String input) { ... } This runs the test twice: once with null, once with empty string.
Result
Tests run with both null and empty inputs without extra annotations.
Knowing this saves time and reduces errors by ensuring both edge cases are always tested together.
5
AdvancedSupported Types and Empty Values
🤔Before reading on: do you think @NullAndEmptySource works with all data types? Commit to yes or no.
Concept: @NullAndEmptySource supports multiple data types and defines what empty means for each.
@NullAndEmptySource works with String, Collections, Maps, and arrays. For example, empty means "" for String, empty list for Collection, empty map for Map, and empty array for arrays. It does not work with primitive types like int or boolean because they cannot be null.
Result
You understand which inputs @NullAndEmptySource can generate and which it cannot.
Knowing supported types prevents confusion and test failures when using this annotation.
6
ExpertCombining @NullAndEmptySource with Other Sources
🤔Before reading on: can @NullAndEmptySource be combined with @ValueSource in one test? Commit to yes or no.
Concept: Learn how to combine @NullAndEmptySource with other input sources to test multiple edge and normal cases together.
JUnit 5 allows combining @NullAndEmptySource with @ValueSource or @CsvSource in the same parameterized test. This runs tests with normal values plus null and empty inputs. Example: @ParameterizedTest @ValueSource(strings = {"foo", "bar"}) @NullAndEmptySource void testMultipleInputs(String input) { ... } This runs four times: "foo", "bar", null, and "".
Result
Tests cover normal, null, and empty inputs in one method.
Understanding this combination enables comprehensive testing with minimal code.
Under the Hood
@NullAndEmptySource is a composed annotation that internally combines @NullSource and @EmptySource. When JUnit runs a parameterized test with it, the test framework generates two arguments: one null and one empty value appropriate to the parameter type. It uses reflection to detect the parameter type and supplies the correct empty value (empty string, empty collection, etc.). This happens before the test method runs, so the method receives these inputs as if they were normal parameters.
Why designed this way?
JUnit 5 aimed to reduce boilerplate and improve test readability. Since null and empty inputs are common edge cases, combining them into one annotation reduces annotation clutter and mistakes. Alternatives like writing separate tests or combining @NullSource and @EmptySource manually were more verbose and error-prone. This design balances simplicity and flexibility.
Test Method
┌───────────────────────────────┐
│ @ParameterizedTest             │
│ @NullAndEmptySource           │
│ void test(String input)       │
└─────────────┬─────────────────┘
              │
              ▼
  ┌───────────────────────────┐
  │ JUnit Parameter Resolver   │
  │ - Detects parameter type   │
  │ - Supplies null and empty  │
  │   values                   │
  └─────────────┬─────────────┘
                │
                ▼
  ┌───────────────────────────┐
  │ Test runs twice:           │
  │ 1) input = null            │
  │ 2) input = empty value     │
  └───────────────────────────┘
Myth Busters - 4 Common Misconceptions
Quick: Does @NullAndEmptySource provide empty values for primitive types like int? Commit to yes or no.
Common Belief:I think @NullAndEmptySource works with all parameter types, including primitives like int or boolean.
Tap to reveal reality
Reality:@NullAndEmptySource does NOT support primitive types because they cannot be null or empty. It only works with reference types like String, Collections, Maps, and arrays.
Why it matters:Using it with primitives causes test failures or unexpected behavior, wasting debugging time.
Quick: Does @NullAndEmptySource add multiple empty values like empty list and empty map at once? Commit to yes or no.
Common Belief:I believe @NullAndEmptySource adds all kinds of empty values for every supported type in one test run.
Tap to reveal reality
Reality:@NullAndEmptySource adds only one empty value per test run matching the parameter type. It does not add multiple empty values of different types simultaneously.
Why it matters:Misunderstanding this leads to incorrect test expectations and confusion about test coverage.
Quick: Can @NullAndEmptySource replace all other parameterized test sources? Commit to yes or no.
Common Belief:I think @NullAndEmptySource can replace @ValueSource or @CsvSource for all test inputs.
Tap to reveal reality
Reality:@NullAndEmptySource only adds null and empty inputs. It does not replace sources that provide normal or complex test data.
Why it matters:Relying solely on @NullAndEmptySource misses testing important normal cases, reducing test effectiveness.
Quick: Does combining @NullAndEmptySource with @NullSource cause duplicate null inputs? Commit to yes or no.
Common Belief:Adding @NullAndEmptySource and @NullSource together is fine and just adds more test inputs.
Tap to reveal reality
Reality:Combining @NullAndEmptySource with @NullSource causes duplicate null inputs, leading to redundant test runs.
Why it matters:Duplicate tests waste time and clutter test reports, making debugging harder.
Expert Zone
1
When used with generic types, @NullAndEmptySource relies on runtime type information which may be erased, causing unexpected empty values or test failures.
2
Combining @NullAndEmptySource with other sources requires careful ordering because JUnit runs tests in the order annotations are declared, affecting test execution sequence.
3
Empty values supplied depend on the parameter type's default empty representation, which can be customized by implementing custom argument converters.
When NOT to use
Avoid @NullAndEmptySource when testing primitive types or when you need multiple distinct empty values for different types in one test. Instead, use separate annotations like @NullSource, @EmptySource, or custom argument providers for fine-grained control.
Production Patterns
In production, @NullAndEmptySource is often combined with @ValueSource to cover normal, null, and empty inputs in one test method. Teams use it to enforce consistent edge case testing across APIs, especially for input validation and parsing logic.
Connections
Defensive Programming
Builds-on
Knowing @NullAndEmptySource helps enforce defensive programming by automatically testing how code handles missing or empty inputs, a core defensive practice.
Test-Driven Development (TDD)
Supports
Using @NullAndEmptySource supports TDD by making it easy to write failing tests for null and empty cases before implementing code to handle them.
Input Validation in Security
Builds-on
Testing null and empty inputs with @NullAndEmptySource helps catch input validation flaws that could lead to security vulnerabilities like injection or crashes.
Common Pitfalls
#1Forgetting to use @NullAndEmptySource leads to missing tests for null and empty inputs.
Wrong approach:@ParameterizedTest @ValueSource(strings = {"foo", "bar"}) void test(String input) { // no null or empty tests }
Correct approach:@ParameterizedTest @ValueSource(strings = {"foo", "bar"}) @NullAndEmptySource void test(String input) { // tests include null and empty }
Root cause:Not realizing null and empty inputs are common edge cases that need explicit testing.
#2Using @NullAndEmptySource on primitive parameters causes errors.
Wrong approach:@ParameterizedTest @NullAndEmptySource void test(int number) { // fails because int can't be null or empty }
Correct approach:@ParameterizedTest @ValueSource(ints = {0, 1, 2}) void test(int number) { // tests normal int values only }
Root cause:Misunderstanding that primitives cannot be null or empty, so annotation is incompatible.
#3Combining @NullAndEmptySource with @NullSource causes duplicate null tests.
Wrong approach:@ParameterizedTest @NullSource @NullAndEmptySource void test(String input) { // null input tested twice }
Correct approach:@ParameterizedTest @NullAndEmptySource void test(String input) { // null and empty tested once each }
Root cause:Not knowing that @NullAndEmptySource already includes null input.
Key Takeaways
@NullAndEmptySource is a JUnit 5 annotation that automatically supplies null and empty inputs to parameterized tests, simplifying edge case testing.
It supports reference types like String, Collections, Maps, and arrays but not primitive types because they cannot be null or empty.
Combining @NullAndEmptySource with other sources like @ValueSource allows comprehensive testing of normal, null, and empty inputs in one test method.
Misusing or misunderstanding this annotation can cause duplicate tests or test failures, so knowing its behavior and limits is essential.
Using @NullAndEmptySource helps catch common bugs early by ensuring null and empty inputs are always tested, improving software robustness.