unit-test-utility-methods

Unit Testing Utility Classes and Static Methods

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "unit-test-utility-methods" with this command: npx skills add giuseppe-trisciuoglio/developer-kit/giuseppe-trisciuoglio-developer-kit-unit-test-utility-methods

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.

When to Use

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

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).

  1. Test Happy Path

Write tests for typical use cases with valid inputs to verify correct behavior.

  1. Test Edge Cases

Test null inputs, empty strings, zero values, and boundary conditions.

  1. Test Error Cases

Verify proper exception throwing for invalid inputs when applicable.

  1. Use Descriptive Test Names

Name tests to clearly indicate what scenario is being tested (e.g., shouldCapitalizeFirstLetter).

  1. Use AssertJ Assertions

Leverage AssertJ's readable assertion methods for clear test code.

  1. Consider Parameterized Tests

Use @ParameterizedTest for testing multiple similar scenarios with different inputs.

Examples

Basic Pattern: Static Utility Testing

Simple String Utility

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

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

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

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

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

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

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)

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

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

  • 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

Examples

Input: Utility Method Without Tests

public class StringUtils { public static boolean isEmpty(String str) { return str == null || str.trim().isEmpty(); } }

Output: Complete Test Coverage

class StringUtilsTest { @Test void shouldReturnTrueForNullString() { assertThat(StringUtils.isEmpty(null)).isTrue(); }

@Test
void shouldReturnTrueForEmptyString() {
    assertThat(StringUtils.isEmpty("")).isTrue();
}

@Test
void shouldReturnTrueForWhitespaceOnly() {
    assertThat(StringUtils.isEmpty("   ")).isTrue();
}

@Test
void shouldReturnFalseForNonEmptyString() {
    assertThat(StringUtils.isEmpty("hello")).isFalse();
}

}

Input: Calculation Without Edge Case Testing

public static double percentage(int value, int total) { return (value / total) * 100.0; }

Output: Tests Covering Edge Cases

class MathUtilsTest { @Test void shouldCalculateNormalPercentage() { assertThat(MathUtils.percentage(25, 100)).isEqualTo(25.0); }

@Test
void shouldHandleZeroDivisor() {
    assertThat(MathUtils.percentage(50, 0)).isEqualTo(0.0);
}

@Test
void shouldHandleZeroValue() {
    assertThat(MathUtils.percentage(0, 100)).isEqualTo(0.0);
}

}

Constraints and Warnings

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

  • AssertJ Assertions

  • Testing Edge Cases and Boundaries

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

Coding

shadcn-ui

No summary provided by upstream source.

Repository SourceNeeds Review
Coding

tailwind-css-patterns

No summary provided by upstream source.

Repository SourceNeeds Review
Coding

unit-test-bean-validation

No summary provided by upstream source.

Repository SourceNeeds Review