Initial commit

This commit is contained in:
Zhongwei Li
2025-11-29 18:28:30 +08:00
commit 171acedaa4
220 changed files with 85967 additions and 0 deletions

View File

@@ -0,0 +1,389 @@
---
name: unit-test-utility-methods
description: Unit tests for utility/helper classes and static methods. Test pure functions and helper logic. Use when validating utility code correctness.
category: testing
tags: [junit-5, unit-testing, utility, static-methods, pure-functions]
version: 1.0.1
---
# Unit Testing Utility Classes and Static Methods
Test static utility methods using JUnit 5. Focus on pure functions without side effects, edge cases, and boundary conditions.
## When to Use This Skill
Use this skill when:
- Testing utility classes with static helper 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
## Basic Pattern: Static Utility Testing
### Simple String Utility
```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 shouldParsePhoneNumber() {
String result = FormatUtils.parsePhoneNumber("5551234567");
assertThat(result).isEqualTo("(555) 123-4567");
}
@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 shouldSluggifyString() {
String result = FormatUtils.sluggify("Hello World! 123");
assertThat(result).isEqualTo("hello-world-123");
}
}
```
## Testing Data Validation
### Validator Utilities
```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
### Multiple Test Cases with @ParameterizedTest
```java
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
import org.junit.jupiter.params.provider.CsvSource;
class StringUtilsParametrizedTest {
@ParameterizedTest
@ValueSource(strings = {"", " ", "null", "undefined"})
void shouldConsiderFalsyValuesAsEmpty(String input) {
boolean result = StringUtils.isEmpty(input);
assertThat(result).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);
}
}
```
## Testing with Mockito for External Dependencies
### Utility with Dependency (Rare Case)
```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 {
@Mock
private Clock clock;
@Test
void shouldGetCurrentDateFromClock() {
Instant fixedTime = Instant.parse("2024-01-15T10:30:00Z");
when(clock.instant()).thenReturn(fixedTime);
LocalDate result = DateUtils.today(clock);
assertThat(result).isEqualTo(LocalDate.of(2024, 1, 15));
}
}
```
## Edge Cases and Boundary Testing
```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
## Troubleshooting
**Floating point precision issues**: Use `isCloseTo()` with delta instead of exact equality.
**Null handling inconsistency**: Decide whether utility returns null or throws exception, then test consistently.
**Complex utility logic belongs elsewhere**: Consider refactoring into testable units.
## References
- [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)