unit-testing-patterns

Unit Testing Patterns Skill

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-testing-patterns" with this command: npx skills add hack23/cia/hack23-cia-unit-testing-patterns

Unit Testing Patterns Skill

Purpose

Write effective, maintainable unit tests using JUnit 5, Mockito, and AssertJ that achieve 80%+ code coverage.

When to Use

  • ✅ Testing business logic in services

  • ✅ Testing data validation

  • ✅ Testing utility functions

  • ✅ Test-driven development (TDD)

JUnit 5 Patterns

Test Structure (AAA Pattern)

@ExtendWith(MockitoExtension.class) class PoliticianServiceTest {

@Mock
private PoliticianRepository repository;

@Mock
private AuditLogger auditLogger;

@InjectMocks
private PoliticianService service;

@Test
@DisplayName("Should find politician by ID when exists")
void shouldFindPoliticianById() {
    // Arrange
    String politicianId = "196401011234";
    Politician expected = new Politician(politicianId, "John", "Doe");
    when(repository.findById(politicianId)).thenReturn(Optional.of(expected));
    
    // Act
    Politician actual = service.findById(politicianId);
    
    // Assert
    assertThat(actual).isNotNull();
    assertThat(actual.getId()).isEqualTo(politicianId);
    assertThat(actual.getFirstName()).isEqualTo("John");
}

@Test
@DisplayName("Should throw exception when politician not found")
void shouldThrowExceptionWhenNotFound() {
    // Arrange
    String politicianId = "999999999999";
    when(repository.findById(politicianId)).thenReturn(Optional.empty());
    
    // Act & Assert
    assertThatThrownBy(() -> service.findById(politicianId))
        .isInstanceOf(ResourceNotFoundException.class)
        .hasMessageContaining("Politician not found");
}

}

Parameterized Tests

@ParameterizedTest @CsvSource({ "196401011234, true", "999999999999, true", "12345, false", "abcdefghijkl, false", "'', false" }) @DisplayName("Should validate Swedish personal ID format") void shouldValidatePersonalIdFormat(String personalId, boolean expected) { assertThat(validator.isValidPersonalId(personalId)).isEqualTo(expected); }

@ParameterizedTest @ValueSource(strings = {"password", "12345678", "qwerty", "admin"}) @DisplayName("Should reject common passwords") void shouldRejectCommonPasswords(String password) { assertThat(passwordValidator.isWeak(password)).isTrue(); }

Test Lifecycle

class DatabaseTest {

@BeforeAll
static void setupClass() {
    // Run once before all tests
    initializeTestDatabase();
}

@BeforeEach
void setup() {
    // Run before each test
    clearDatabase();
}

@AfterEach
void teardown() {
    // Run after each test
    rollbackTransaction();
}

@AfterAll
static void teardownClass() {
    // Run once after all tests
    closeConnections();
}

}

Mockito Best Practices

Stubbing

@Test void shouldReturnCachedData() { // Stub method to return specific value when(cache.get("key")).thenReturn("value");

// Stub with argument matchers
when(repository.findByName(anyString())).thenReturn(new ArrayList<>());

// Stub with custom answer
when(service.process(any())).thenAnswer(invocation -> {
    String arg = invocation.getArgument(0);
    return arg.toUpperCase();
});

// Stub to throw exception
when(repository.findById("invalid"))
    .thenThrow(new IllegalArgumentException("Invalid ID"));

}

Verification

@Test void shouldLogAuditEvent() { // Arrange Politician politician = new Politician("123", "John", "Doe");

// Act
service.create(politician);

// Verify method was called once
verify(auditLogger).log(eq("CREATE_POLITICIAN"), any(Politician.class));

// Verify exact number of calls
verify(repository, times(1)).save(politician);

// Verify never called
verify(emailService, never()).sendNotification(any());

// Verify no more interactions
verifyNoMoreInteractions(auditLogger);

}

Argument Captors

@Test void shouldSaveWithCorrectTimestamp() { ArgumentCaptor<Politician> captor = ArgumentCaptor.forClass(Politician.class);

service.create(new Politician("123", "John", "Doe"));

verify(repository).save(captor.capture());

Politician saved = captor.getValue();
assertThat(saved.getCreatedAt()).isCloseTo(
    LocalDateTime.now(),
    within(1, ChronoUnit.SECONDS)
);

}

AssertJ Fluent Assertions

@Test void shouldHaveCorrectProperties() { Politician politician = service.findById("123");

// Fluent assertions
assertThat(politician)
    .isNotNull()
    .hasFieldOrPropertyWithValue("firstName", "John")
    .hasFieldOrProperty("lastName")
    .extracting("party")
    .isEqualTo("S");

// Collection assertions
List&#x3C;Politician> politicians = service.findAll();
assertThat(politicians)
    .isNotEmpty()
    .hasSize(5)
    .extracting("firstName")
    .contains("John", "Jane")
    .doesNotContain("Invalid");

// Exception assertions
assertThatThrownBy(() -> service.delete("invalid"))
    .isInstanceOf(NotFoundException.class)
    .hasMessage("Politician not found")
    .hasNoCause();

}

Code Coverage

Target: 80% Line Coverage, 70% Branch Coverage

<!-- pom.xml --> <plugin> <groupId>org.jacoco</groupId> <artifactId>jacoco-maven-plugin</artifactId> <version>${jacoco.version}</version> <executions> <execution> <goals> <goal>prepare-agent</goal> </goals> </execution> <execution> <id>report</id> <phase>test</phase> <goals> <goal>report</goal> </goals> </execution> <execution> <id>check</id> <goals> <goal>check</goal> </goals> <configuration> <rules> <rule> <element>BUNDLE</element> <limits> <limit> <counter>LINE</counter> <value>COVEREDRATIO</value> <minimum>0.80</minimum> </limit> <limit> <counter>BRANCH</counter> <value>COVEREDRATIO</value> <minimum>0.70</minimum> </limit> </limits> </rule> </rules> </configuration> </execution> </executions> </plugin>

Test Organization

src/test/java/ └── com/hack23/cia/ ├── service/ │ ├── impl/ │ │ ├── PoliticianServiceTest.java │ │ └── VotingAnalysisServiceTest.java │ └── api/ │ └── PoliticianServiceIT.java (Integration tests) ├── util/ │ ├── DateUtilsTest.java │ └── ValidationUtilsTest.java └── testfoundation/ ├── TestDataFactory.java └── AbstractServiceTest.java

References

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.

General

incident-response

No summary provided by upstream source.

Repository SourceNeeds Review
General

secrets-management

No summary provided by upstream source.

Repository SourceNeeds Review
General

ai governance

No summary provided by upstream source.

Repository SourceNeeds Review