unit-test-utility-methods ยท diff
git:20260316.91dc3c4 to git:20260324.a02e282
141 added, 404 removed. Audit A to A.
---
name: unit-test-utility-methods
- description: Provides patterns for unit testing utility/helper classes and static methods. Validates pure functions and helper logic. Use when verifying utility code correctness.
+ description: Provides patterns for testing utility classes, static methods, and helper functions. Validates pure functions, null handling, edge cases, and boundary conditions. Generates AssertJ assertions and @ParameterizedTest for string utils, math utils, validators, and collection helpers. Use when testing utils, test helpers, helper functions, static methods, or verifying utility code correctness.
allowed-tools: Read, Write, Bash, Glob, Grep
---
# Unit Testing Utility Classes and Static Methods
## Overview
- This skill provides comprehensive patterns for testing utility classes and static methods using JUnit 5. It covers testing pure functions without side effects, edge case handling, boundary conditions, and common utility patterns like string manipulation, calculations, collections, and data validation.
+ This skill generates tests for utility classes with static helper methods and pure functions. It provides patterns for testing null handling, edge cases, boundary conditions, and common utilities like string manipulation, calculations, data validation, and collections. Pure functions require no mocking.
## When to Use
Use this skill when:
- - Testing utility classes with static helper methods
+ - Writing tests for utility/helper classes with static methods
- Testing pure functions with no state or side effects
- - Testing string manipulation and formatting utilities
- - Testing calculation and conversion utilities
- - Testing collections and array utilities
- - Want simple, fast tests without mocking complexity
- - Testing data transformation and validation helpers
+ - Testing string manipulation, formatting, or transformation utilities
+ - Testing calculation, conversion, or math helper functions
+ - Testing data validation and formatter utilities
+ - Verifying null/empty input handling in utility code
+ - Testing collections or array helper methods
## Instructions
- Follow these steps to test utility classes and static methods:
-
- ### 1. Create Test Class
-
- Create a JUnit 5 test class named after the utility class being tested (e.g., StringUtilsTest).
-
- ### 2. Test Happy Path
-
- Write tests for typical use cases with valid inputs to verify correct behavior.
-
- ### 3. Test Edge Cases
-
- Test null inputs, empty strings, zero values, and boundary conditions.
-
- ### 4. Test Error Cases
-
- Verify proper exception throwing for invalid inputs when applicable.
-
- ### 5. Use Descriptive Test Names
-
- Name tests to clearly indicate what scenario is being tested (e.g., shouldCapitalizeFirstLetter).
-
- ### 6. Use AssertJ Assertions
-
- Leverage AssertJ's readable assertion methods for clear test code.
-
- ### 7. Consider Parameterized Tests
-
- Use `@`ParameterizedTest for testing multiple similar scenarios with different inputs.
+ 1. **Create test class**: Name it after the utility (e.g., `StringUtilsTest`)
+ 2. **Test happy path**: Valid inputs with expected outputs
+ 3. **Test edge cases**: null, empty, whitespace, single elements
+ 4. **Test boundary conditions**: max/min values, large numbers, precision
+ 5. **Use descriptive names**: `shouldCapitalizeFirstLetter` instead of `test1`
+ 6. **Use AssertJ**: For readable, chainable assertions
+ 7. **Use `@ParameterizedTest`**: For multiple similar inputs (see `references/parameterized-tests.md`)
+ 8. **Avoid mocking**: Pure utilities need no mocks
## Examples
- ## Basic Pattern: Static Utility Testing
-
- ### Simple String Utility
+ ### Basic Static Utility Test
```java
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.*;
class StringUtilsTest {
- @Test
- void shouldCapitalizeFirstLetter() {
- String result = StringUtils.capitalize("hello");
- assertThat(result).isEqualTo("Hello");
- }
-
- @Test
- void shouldHandleEmptyString() {
- String result = StringUtils.capitalize("");
- assertThat(result).isEmpty();
- }
-
- @Test
- void shouldHandleNullInput() {
- String result = StringUtils.capitalize(null);
- assertThat(result).isNull();
- }
-
- @Test
- void shouldHandleSingleCharacter() {
- String result = StringUtils.capitalize("a");
- assertThat(result).isEqualTo("A");
- }
-
- @Test
- void shouldNotChangePascalCase() {
- String result = StringUtils.capitalize("Hello");
- assertThat(result).isEqualTo("Hello");
- }
- }
- ```
-
- ## Testing Null Handling
-
- ### Null-Safe Utility Methods
-
- ```java
- class NullSafeUtilsTest {
-
- @Test
- void shouldReturnDefaultValueWhenNull() {
- Object result = NullSafeUtils.getOrDefault(null, "default");
- assertThat(result).isEqualTo("default");
- }
-
- @Test
- void shouldReturnValueWhenNotNull() {
- Object result = NullSafeUtils.getOrDefault("value", "default");
- assertThat(result).isEqualTo("value");
- }
-
- @Test
- void shouldReturnFalseWhenStringIsNull() {
- boolean result = NullSafeUtils.isNotBlank(null);
- assertThat(result).isFalse();
- }
-
- @Test
- void shouldReturnTrueWhenStringHasContent() {
- boolean result = NullSafeUtils.isNotBlank(" text ");
- assertThat(result).isTrue();
- }
- }
- ```
-
- ## Testing Calculations and Conversions
-
- ### Math Utilities
-
- ```java
- class MathUtilsTest {
-
- @Test
- void shouldCalculatePercentage() {
- double result = MathUtils.percentage(25, 100);
- assertThat(result).isEqualTo(25.0);
- }
-
- @Test
- void shouldHandleZeroDivisor() {
- double result = MathUtils.percentage(50, 0);
- assertThat(result).isZero();
- }
-
- @Test
- void shouldRoundToTwoDecimalPlaces() {
- double result = MathUtils.round(3.14159, 2);
- assertThat(result).isEqualTo(3.14);
- }
-
- @Test
- void shouldHandleNegativeNumbers() {
- int result = MathUtils.absoluteValue(-42);
- assertThat(result).isEqualTo(42);
- }
- }
- ```
-
- ## Testing Collection Utilities
-
- ### List/Set/Map Operations
-
- ```java
- class CollectionUtilsTest {
-
- @Test
- void shouldFilterList() {
- List<Integer> numbers = List.of(1, 2, 3, 4, 5);
- List<Integer> evenNumbers = CollectionUtils.filter(numbers, n -> n % 2 == 0);
- assertThat(evenNumbers).containsExactly(2, 4);
- }
-
- @Test
- void shouldReturnEmptyListWhenNoMatches() {
- List<Integer> numbers = List.of(1, 3, 5);
- List<Integer> evenNumbers = CollectionUtils.filter(numbers, n -> n % 2 == 0);
- assertThat(evenNumbers).isEmpty();
- }
-
- @Test
- void shouldHandleNullList() {
- List<Integer> result = CollectionUtils.filter(null, n -> true);
- assertThat(result).isEmpty();
- }
-
- @Test
- void shouldJoinStringsWithSeparator() {
- String result = CollectionUtils.join(List.of("a", "b", "c"), "-");
- assertThat(result).isEqualTo("a-b-c");
- }
-
- @Test
- void shouldHandleEmptyList() {
- String result = CollectionUtils.join(List.of(), "-");
- assertThat(result).isEmpty();
- }
-
- @Test
- void shouldDeduplicateList() {
- List<String> input = List.of("apple", "banana", "apple", "cherry", "banana");
- Set<String> unique = CollectionUtils.deduplicate(input);
- assertThat(unique).containsExactlyInAnyOrder("apple", "banana", "cherry");
- }
- }
- ```
-
- ## Testing String Transformations
-
- ### Format and Parse Utilities
-
- ```java
- class FormatUtilsTest {
-
- @Test
- void shouldFormatCurrencyWithSymbol() {
- String result = FormatUtils.formatCurrency(1234.56);
- assertThat(result).isEqualTo("$1,234.56");
- }
-
- @Test
- void shouldHandleNegativeCurrency() {
- String result = FormatUtils.formatCurrency(-100.00);
- assertThat(result).isEqualTo("-$100.00");
- }
+ @Test
+ void shouldCapitalizeFirstLetter() {
+ assertThat(StringUtils.capitalize("hello")).isEqualTo("Hello");
+ }
- @Test
- void shouldParsePhoneNumber() {
- String result = FormatUtils.parsePhoneNumber("5551234567");
- assertThat(result).isEqualTo("(555) 123-4567");
- }
+ @Test
+ void shouldReturnNullForNullInput() {
+ assertThat(StringUtils.capitalize(null)).isNull();
+ }
- @Test
- void shouldFormatDate() {
- LocalDate date = LocalDate.of(2024, 1, 15);
- String result = FormatUtils.formatDate(date, "yyyy-MM-dd");
- assertThat(result).isEqualTo("2024-01-15");
- }
+ @Test
+ void shouldHandleEmptyString() {
+ assertThat(StringUtils.capitalize("")).isEmpty();
+ }
- @Test
- void shouldSluggifyString() {
- String result = FormatUtils.sluggify("Hello World! 123");
- assertThat(result).isEqualTo("hello-world-123");
- }
+ @Test
+ void shouldHandleSingleCharacter() {
+ assertThat(StringUtils.capitalize("a")).isEqualTo("A");
+ }
}
```
- ## Testing Data Validation
-
- ### Validator Utilities
+ ### Comprehensive Example: isEmpty Implementation
```java
- class ValidatorUtilsTest {
-
- @Test
- void shouldValidateEmailFormat() {
- boolean valid = ValidatorUtils.isValidEmail("user@example.com");
- assertThat(valid).isTrue();
-
- boolean invalid = ValidatorUtils.isValidEmail("invalid-email");
- assertThat(invalid).isFalse();
- }
-
- @Test
- void shouldValidatePhoneNumber() {
- boolean valid = ValidatorUtils.isValidPhone("555-123-4567");
- assertThat(valid).isTrue();
-
- boolean invalid = ValidatorUtils.isValidPhone("12345");
- assertThat(invalid).isFalse();
- }
-
- @Test
- void shouldValidateUrlFormat() {
- boolean valid = ValidatorUtils.isValidUrl("https://example.com");
- assertThat(valid).isTrue();
-
- boolean invalid = ValidatorUtils.isValidUrl("not a url");
- assertThat(invalid).isFalse();
- }
-
- @Test
- void shouldValidateCreditCardNumber() {
- boolean valid = ValidatorUtils.isValidCreditCard("4532015112830366");
- assertThat(valid).isTrue();
-
- boolean invalid = ValidatorUtils.isValidCreditCard("1234567890123456");
- assertThat(invalid).isFalse();
- }
- }
- ```
-
- ## Testing Parameterized Scenarios
+ // Input: public static boolean isEmpty(String str)
+ // { return str == null || str.trim().isEmpty(); }
- ### Multiple Test Cases with `@`ParameterizedTest
+ class StringUtilsTest {
- ```java
- import org.junit.jupiter.params.ParameterizedTest;
- import org.junit.jupiter.params.provider.ValueSource;
- import org.junit.jupiter.params.provider.CsvSource;
+ @Test
+ void shouldReturnTrueForNullString() {
+ assertThat(StringUtils.isEmpty(null)).isTrue();
+ }
- class StringUtilsParametrizedTest {
+ @Test
+ void shouldReturnTrueForEmptyString() {
+ assertThat(StringUtils.isEmpty("")).isTrue();
+ }
- @ParameterizedTest
- @ValueSource(strings = {"", " ", "null", "undefined"})
- void shouldConsiderFalsyValuesAsEmpty(String input) {
- boolean result = StringUtils.isEmpty(input);
- assertThat(result).isTrue();
- }
+ @Test
+ void shouldReturnTrueForWhitespaceOnly() {
+ assertThat(StringUtils.isEmpty(" ")).isTrue();
+ }
- @ParameterizedTest
- @CsvSource({
- "hello,HELLO",
- "world,WORLD",
- "javaScript,JAVASCRIPT",
- "123ABC,123ABC"
- })
- void shouldConvertToUpperCase(String input, String expected) {
- String result = StringUtils.toUpperCase(input);
- assertThat(result).isEqualTo(expected);
- }
+ @Test
+ void shouldReturnFalseForNonEmptyString() {
+ assertThat(StringUtils.isEmpty("hello")).isFalse();
+ }
}
```
- ## Testing with Mockito for External Dependencies
-
- ### Utility with Dependency (Rare Case)
+ ### Null-Safe Utility
```java
- import org.junit.jupiter.api.extension.ExtendWith;
- import org.mockito.Mock;
- import org.mockito.junit.jupiter.MockitoExtension;
- import static org.mockito.Mockito.when;
-
- @ExtendWith(MockitoExtension.class)
- class DateUtilsTest {
+ class NullSafeUtilsTest {
- @Mock
- private Clock clock;
+ @Test
+ void shouldReturnDefaultWhenNull() {
+ assertThat(NullSafeUtils.getOrDefault(null, "default")).isEqualTo("default");
+ }
- @Test
- void shouldGetCurrentDateFromClock() {
- Instant fixedTime = Instant.parse("2024-01-15T10:30:00Z");
- when(clock.instant()).thenReturn(fixedTime);
+ @Test
+ void shouldReturnValueWhenNotNull() {
+ assertThat(NullSafeUtils.getOrDefault("value", "default")).isEqualTo("value");
+ }
- LocalDate result = DateUtils.today(clock);
-
- assertThat(result).isEqualTo(LocalDate.of(2024, 1, 15));
- }
+ @Test
+ void shouldReturnFalseWhenBlank() {
+ assertThat(NullSafeUtils.isNotBlank(null)).isFalse();
+ assertThat(NullSafeUtils.isNotBlank(" ")).isFalse();
+ }
}
```
- ## Edge Cases and Boundary Testing
+ ### Math/Calculation Utility
```java
- class MathUtilsEdgeCaseTest {
-
- @Test
- void shouldHandleMaxIntegerValue() {
- int result = MathUtils.increment(Integer.MAX_VALUE);
- assertThat(result).isEqualTo(Integer.MAX_VALUE);
- }
-
- @Test
- void shouldHandleMinIntegerValue() {
- int result = MathUtils.decrement(Integer.MIN_VALUE);
- assertThat(result).isEqualTo(Integer.MIN_VALUE);
- }
-
- @Test
- void shouldHandleVeryLargeNumbers() {
- BigDecimal result = MathUtils.add(
- new BigDecimal("999999999999.99"),
- new BigDecimal("0.01")
- );
- assertThat(result).isEqualTo(new BigDecimal("1000000000000.00"));
- }
-
- @Test
- void shouldHandleFloatingPointPrecision() {
- double result = MathUtils.multiply(0.1, 0.2);
- assertThat(result).isCloseTo(0.02, within(0.0001));
- }
- }
- ```
-
- ## Best Practices
-
- - **Test pure functions exclusively** - no side effects or state
- - **Cover happy path and edge cases** - null, empty, extreme values
- - **Use descriptive test names** - clearly state what's being tested
- - **Keep tests simple and short** - utility tests should be quick to understand
- - **Use `@`ParameterizedTest** for testing multiple similar scenarios
- - **Avoid mocking when not needed** - only mock external dependencies
- - **Test boundary conditions** - min/max values, empty collections, null inputs
-
- ## Common Pitfalls
-
- - Testing framework behavior instead of utility logic
- - Over-mocking when pure functions need no mocks
- - Not testing null/empty edge cases
- - Not testing negative numbers and extreme values
- - Test methods too large - split complex scenarios
-
- ## Constraints and Warnings
+ class MathUtilsTest {
- - **Static methods cannot be mocked**: Use reflection-based utilities like PowerMock only when absolutely necessary
- - **Pure function requirement**: Utility methods should be pure functions; testing stateful utilities is difficult
- - **Floating point precision**: Never use exact equality for floating point comparisons; use tolerance
- - **Null handling consistency**: Decide whether utilities return null or throw exceptions for invalid input
- - **Immutable inputs**: Document clearly whether utility methods modify input parameters
- - **Thread safety**: Static utilities must be thread-safe; verify under concurrent access
- - **External dependencies**: Minimize external dependencies in utility classes for easier testing
+ @Test
+ void shouldCalculatePercentage() {
+ assertThat(MathUtils.percentage(25, 100)).isEqualTo(25.0);
+ }
- ## Examples
+ @Test
+ void shouldHandleZeroDivisor() {
+ assertThat(MathUtils.percentage(50, 0)).isZero();
+ }
- ### Input: Utility Method Without Tests
+ @Test
+ void shouldRoundToDecimalPlaces() {
+ assertThat(MathUtils.round(3.14159, 2)).isEqualTo(3.14);
+ }
- ```java
- public class StringUtils {
- public static boolean isEmpty(String str) {
- return str == null || str.trim().isEmpty();
+ @Test
+ void shouldHandleFloatingPointWithTolerance() {
+ assertThat(MathUtils.multiply(0.1, 0.2))
+ .isCloseTo(0.02, within(0.0001));
}
}
```
- ### Output: Complete Test Coverage
+ ### Collection Utility
```java
- class StringUtilsTest {
+ class CollectionUtilsTest {
+
@Test
- void shouldReturnTrueForNullString() {
- assertThat(StringUtils.isEmpty(null)).isTrue();
+ void shouldFilterList() {
+ List<Integer> result = CollectionUtils.filter(List.of(1, 2, 3, 4), n -> n % 2 == 0);
+ assertThat(result).containsExactly(2, 4);
}
@Test
- void shouldReturnTrueForEmptyString() {
- assertThat(StringUtils.isEmpty("")).isTrue();
+ void shouldHandleNullList() {
+ assertThat(CollectionUtils.filter(null, n -> true)).isEmpty();
}
@Test
- void shouldReturnTrueForWhitespaceOnly() {
- assertThat(StringUtils.isEmpty(" ")).isTrue();
+ void shouldJoinWithSeparator() {
+ assertThat(CollectionUtils.join(List.of("a", "b", "c"), "-")).isEqualTo("a-b-c");
}
@Test
- void shouldReturnFalseForNonEmptyString() {
- assertThat(StringUtils.isEmpty("hello")).isFalse();
+ void shouldDeduplicateList() {
+ assertThat(CollectionUtils.deduplicate(List.of("a", "b", "a")))
+ .containsExactlyInAnyOrder("a", "b");
}
}
```
- ### Input: Calculation Without Edge Case Testing
+ ### Data Validation Utility
```java
- public static double percentage(int value, int total) {
- return (value / total) * 100.0;
- }
- ```
-
- ### Output: Tests Covering Edge Cases
+ class ValidatorUtilsTest {
- ```java
- class MathUtilsTest {
@Test
- void shouldCalculateNormalPercentage() {
- assertThat(MathUtils.percentage(25, 100)).isEqualTo(25.0);
+ void shouldValidateEmailFormat() {
+ assertThat(ValidatorUtils.isValidEmail("user@example.com")).isTrue();
+ assertThat(ValidatorUtils.isValidEmail("invalid")).isFalse();
}
@Test
- void shouldHandleZeroDivisor() {
- assertThat(MathUtils.percentage(50, 0)).isEqualTo(0.0);
+ void shouldValidateUrlFormat() {
+ assertThat(ValidatorUtils.isValidUrl("https://example.com")).isTrue();
+ assertThat(ValidatorUtils.isValidUrl("not a url")).isFalse();
}
@Test
- void shouldHandleZeroValue() {
- assertThat(MathUtils.percentage(0, 100)).isEqualTo(0.0);
+ void shouldValidateCreditCard() {
+ assertThat(ValidatorUtils.isValidCreditCard("4532015112830366")).isTrue();
+ assertThat(ValidatorUtils.isValidCreditCard("1234567890123456")).isFalse();
}
}
```
- ## Constraints and Warnings
+ ### Utility with Clock Dependency (Rare)
- **Floating point precision issues**: Use `isCloseTo()` with delta instead of exact equality.
+ ```java
+ @ExtendWith(MockitoExtension.class)
+ class DateUtilsTest {
- **Null handling inconsistency**: Decide whether utility returns null or throws exception, then test consistently.
+ @Mock
+ private Clock clock;
- **Complex utility logic belongs elsewhere**: Consider refactoring into testable units.
+ @Test
+ void shouldGetDateFromClock() {
+ when(clock.instant()).thenReturn(Instant.parse("2024-01-15T10:00:00Z"));
+ assertThat(DateUtils.today(clock)).isEqualTo(LocalDate.of(2024, 1, 15));
+ }
+ }
+ ```
- ## References
+ ## Best Practices
- - [JUnit 5 Parameterized Tests](https://junit.org/junit5/docs/current/user-guide/#writing-tests-parameterized-tests)
- - [AssertJ Assertions](https://assertj.github.io/assertj-core-features-highlight.html)
- - [Testing Edge Cases and Boundaries](https://www.baeldung.com/testing-properties-methods-using-mockito)
+ - **Test pure functions exclusively** - no side effects or state dependency
+ - **Cover happy path and edge cases** - null, empty, whitespace, extreme values
+ - **Use descriptive test names** - `shouldReturnNullWhenInputIsNull`
+ - **Use `@ParameterizedTest`** for multiple similar inputs (see `references/parameterized-tests.md`)
+ - **Test boundary conditions** - min/max values, overflow, precision
+ - **Avoid mocking pure functions** - only mock external dependencies like Clock
+ - **Keep tests independent** - no order dependency between tests
+
+ ## Constraints and Warnings
+
+ - **No mocking static methods**: Use reflection utilities only when absolutely necessary
+ - **Pure function requirement**: Stateful utilities are harder to test; prefer immutability
+ - **Floating point precision**: Never use exact equality; use `isCloseTo(delta)`
+ - **Null handling consistency**: Decide whether utility returns null or throws; test accordingly
+ - **Thread safety**: Static utilities must be thread-safe; verify concurrent behavior separately
+ - **Immutable inputs**: Document whether utilities modify input parameters
+ - **Edge cases reference**: See `references/edge-cases.md` for boundary testing patterns