| name | unit-testing |
| description | Use when writing or reviewing Java unit tests. Enforces Mockito/JUnit 5 best practices - strict stubbing, no lenient mode, specific matchers, complete flow stubbing, Arrange-Act-Assert structure, and clear test naming. |
| license | Apache-2.0 |
| metadata | {"author":"folio-org","version":"1.0.0"} |
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();
verify(dataSource).getConnection();
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 {
setupCommonMocks();
var service = new MyService(dependency1);
when(dependency1.doSomething()).thenReturn(expectedValue);
var result = service.methodUnderTest();
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