unit-testing
npx skills add https://github.com/folio-org/folio-eureka-ai-dev --skill unit-testing
Agent 安装分布
Skill 文档
Unit Testing Guidelines
General Principles
1. Test Independence
- Each test must be independent and self-contained
- Tests must not rely on execution order
- Use local variables within test methods instead of shared instance variables when possible
2. Clear Test Names
Follow the pattern: methodName_scenario_expectedBehavior
Examples: tableExists_positive_tableIsPresent, deleteTimer_negative_timerDescriptorIdIsNull
3. Test Organization
Structure every test with: Arrange â Act â Assert
Mockito Best Practices
1. Never Use Lenient Mode
â Never add @MockitoSettings(strictness = Strictness.LENIENT) â write precise tests instead.
2. Avoid Unnecessary Stubbing
Only stub (when()) what is actually used in the test. Use helper methods called only by tests that need them:
private void setupContextMocks() {
when(context.getFolioModuleMetadata()).thenReturn(moduleMetadata);
when(context.getTenantId()).thenReturn(TENANT_ID);
when(moduleMetadata.getDBSchemaName(TENANT_ID)).thenReturn(SCHEMA_NAME);
}
3. Remove Redundant Verify Statements
Only verify interactions that are not mocked with when():
verify(connection).close(); // â
not mocked
verify(dataSource).getConnection(); // â redundant â already mocked
4. Prefer Specific Object Stubs Over any() Matchers
Use specific objects when you control test data:
when(mapper.convert(input)).thenReturn(output); // â
when(mapper.convert(any(InputType.class))).thenReturn(output); // â
Use any() only when: the service mutates the object unpredictably, multiple different objects may be passed, or when using argument captors.
5. Use Simple when().thenReturn() for Basic Returns
when(service.process(data)).thenReturn(true); // â
doAnswer(inv -> true).when(service).process(data); // â
Use thenAnswer() only for computed return values, conditional logic, or side effects.
6. Stub the Complete Service Flow
Stub every external call in the code path being tested:
when(repository.findByKey(key)).thenReturn(Optional.empty());
when(repository.save(entity)).thenReturn(entity);
when(repository.findById(id)).thenReturn(Optional.of(entity));
7. Use verifyNoMoreInteractions Carefully
Only include mocks that should be fully accounted for:
@AfterEach
void tearDown() {
verifyNoMoreInteractions(dataSource, connection, databaseMetaData, resultSet);
}
Test Structure
1. Class Setup
@UnitTest
@ExtendWith(MockitoExtension.class)
class MyServiceTest {
private static final String CONSTANT_VALUE = "test-value";
@Mock
private Dependency1 dependency1;
@AfterEach
void tearDown() {
verifyNoMoreInteractions(dependency1);
}
}
2. Individual Test
@Test
void methodName_scenario_expectedBehavior() throws Exception {
// Arrange
setupCommonMocks();
var service = new MyService(dependency1);
when(dependency1.doSomething()).thenReturn(expectedValue);
// Act
var result = service.methodUnderTest();
// Assert
assertThat(result).isEqualTo(expectedValue);
verify(dependency1).close();
}
Verification Patterns
1. Successful Operations
@Test
void operation_positive_success() throws Exception {
setupRequiredMocks();
when(repository.find()).thenReturn(entity);
var result = service.operation();
assertThat(result).isNotNull();
verify(connection).close();
}
2. Exception Scenarios
@Test
void operation_negative_throwsException() throws Exception {
var expectedException = new SQLException("Connection failed");
when(dataSource.getConnection()).thenThrow(expectedException);
assertThatThrownBy(() -> service.operation())
.isInstanceOf(DataRetrievalFailureException.class)
.hasMessageContaining("Failed to perform operation")
.hasCause(expectedException);
}
Common Patterns
1. Constants for Test Data
Define at class level for reuse across tests:
private static final String MODULE_ID = "mod-foo-1.0.0";
private static final String TENANT_ID = "test-tenant";
2. Helper Methods
Extract private static methods for object creation, assertions, finding objects, and mock setup when the same code appears in 2+ tests.
For advanced patterns (parameterized tests, deep copy stubbing, fluent API, exact matching, explicit lambda types), see references/patterns.md.
For complete test class examples, see references/examples.md.
Checklist
- No
@MockitoSettings(strictness = Strictness.LENIENT) - No unnecessary stubbing â all
when()statements are used - Only verify methods that are NOT mocked
- Specific object stubs over
any()when test data is controlled - Simple
when().thenReturn()for basic returns - All repository/service interactions in the code path are stubbed
- Copy operations stubbed when service creates internal copies
- Test names follow
methodName_scenario_expectedBehavior - Each test is independent
- Resources (connections, streams) verified to be closed
- Exception tests verify type, message, and cause
- Constants used for reusable test data
- Helper methods eliminate code duplication
- Clear Arrange-Act-Assert sections
- Fluent API used for test data creation
- Parameterized tests use
Stream<Type>for single parameters - Explicit type declarations for lambdas when var inference fails
- Exact matching tests include similar non-matching values
- All error paths from utility methods covered
- Unused imports removed