Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Provides patterns for unit testing service layer with Mockito. Creates isolated tests that mock repository calls, verify method invocations, test exception scenarios, and stub external API responses. Use when testing service behaviors and business logic without database or external services.
.claude/skills/giuseppe-trisciuoglio-unit-test-service-layer/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-01 | ✗→✓ | ▲ Improved | -7% | 0% |
| case-02 | ✗→✓ | ▲ Improved | 12% | 0% |
| case-03 | ✗→✓ | ▲ Improved | -8% | 0% |
| case-12 | ✗→✓ | ▲ Improved | 16% | 0% |
| case-04 | ✓→✓ | = Same ✓ | 48% | 0% |
Provides patterns for unit testing @Service classes using Mockito. Mocks repository calls, verifies method invocations, tests exception scenarios, and stubs external API responses. Enables fast, isolated tests without Spring container or database.
@Service classesFollow this workflow to test service layer with Mockito, including validation checkpoints:
Use @ExtendWith(MockitoExtension.class) to enable Mockito annotations.
@Mock and @InjectMocksUse @Mock for dependencies (repositories, clients) and @InjectMocks for the service under test.
Arrange: Create test data and configure mock return values using when().thenReturn().
Act: Execute the service method being tested.
Assert:
verify()Configure mocks to throw exceptions with when().thenThrow().
Validation checkpoint: Verify exception type and message
mvn test or gradle testmvn test jacoco:reportjava@ExtendWith(MockitoExtension.class) class UserServiceTest { @Mock private UserRepository userRepository; @InjectMocks private UserService userService; @Test void shouldReturnUserWhenFound() { // Arrange User expected = new User(1L, "Alice"); when(userRepository.findById(1L)).thenReturn(Optional.of(expected)); // Act User result = userService.getUser(1L); // Assert assertThat(result.getName()).isEqualTo("Alice"); verify(userRepository).findById(1L); } @Test void shouldThrowWhenUserNotFound() { // Arrange when(userRepository.findById(999L)).thenReturn(Optional.empty()); // Act & Assert assertThatThrownBy(() -> userService.getUser(999L)) .isInstanceOf(UserNotFoundException.class); } }
java@Test void shouldSendEmailOnUserCreation() { User newUser = new User(1L, "Alice", "alice@example.com"); when(userRepository.save(any(User.class))).thenReturn(newUser); enrichmentService.registerNewUser("Alice", "alice@example.com"); verify(userRepository).save(any(User.class)); verify(emailService).sendWelcomeEmail("alice@example.com"); }
For additional patterns (multiple dependencies, argument captors, async services, InOrder verification), see references/examples.md.
@ExtendWith(MockitoExtension.class) for JUnit 5 integrationexpectedUser, actualUser, captor@Spy; partial mocking is harder to understand and maintain.any(), eq()) cannot be mixed with actual values in the same stub.| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-01 | fail→pass | 15,362 | 9,646 | -37% | 1 | 1 | 0% | 3,262 | 3,043 | -7% | 0 | 0 | — |
case-02 | fail→pass | 28,904 | 10,888 | -62% | 1 | 1 | 0% | 2,937 | 3,293 | +12% | 0 | 0 | — |
case-03 | fail→pass | 15,303 | 9,595 | -37% | 1 | 1 | 0% | 3,294 | 3,017 | -8% | 0 | 0 | — |
case-04 | pass→pass | 12,762 | 12,675 | -1% | 1 | 1 | 0% | 2,499 | 3,698 | +48% | 0 | 0 | — |
case-05 | pass→pass | 9,940 | 13,263 | +33% | 1 | 1 | 0% | 2,257 | 3,778 | +67% | 0 | 0 | — |
case-06 | pass→pass | 10,119 | 6,865 | -32% | 1 | 1 | 0% | 2,094 | 2,373 | +13% | 0 | 0 | — |
case-07 | pass→pass | 12,850 | 6,291 | -51% | 1 | 1 | 0% | 2,023 | 2,524 | +25% | 0 | 0 | — |
case-08 | pass→pass | 7,330 | 4,302 | -41% | 1 | 1 | 0% | 1,278 | 1,906 | +49% | 0 | 0 | — |
case-09 | pass→pass | 10,246 | 3,611 | -65% | 1 | 1 | 0% | 656 | 1,758 | +168% | 0 | 0 | — |
case-10 | pass→pass | 11,860 | 9,016 | -24% | 1 | 1 | 0% | 2,072 | 2,626 | +27% | 0 | 0 | — |
case-11 | pass→pass | 11,219 | 7,422 | -34% | 1 | 1 | 0% | 1,814 | 2,385 | +31% | 0 | 0 | — |
case-12 | fail→pass | 11,056 | 7,827 | -29% | 1 | 1 | 0% | 2,290 | 2,650 | +16% | 0 | 0 | — |
case-13 | pass→pass | 14,528 | 6,243 | -57% | 1 | 1 | 0% | 1,825 | 2,221 | +22% | 0 | 0 | — |
case-14 | pass→pass | 5,742 | 5,758 | +0% | 1 | 1 | 0% | 1,098 | 2,275 | +107% | 0 | 0 | — |
case-15 | pass→pass | 11,240 | 8,684 | -23% | 1 | 1 | 0% | 1,970 | 2,672 | +36% | 0 | 0 | — |
case-16 | pass→pass | 13,194 | 10,495 | -20% | 1 | 1 | 0% | 2,043 | 2,655 | +30% | 0 | 0 | — |
case-17 | pass→pass | 14,128 | 13,122 | -7% | 1 | 1 | 0% | 2,189 | 3,140 | +43% | 0 | 0 | — |
case-18 | pass→pass | 13,047 | 10,083 | -23% | 1 | 1 | 0% | 2,298 | 2,946 | +28% | 0 | 0 | — |
case-19 | pass→pass | 10,016 | 5,246 | -48% | 1 | 1 | 0% | 1,941 | 2,096 | +8% | 0 | 0 | — |
case-20 | pass→pass | 7,313 | 3,724 | -49% | 1 | 1 | 0% | 1,245 | 1,664 | +34% | 0 | 0 | — |
case-21 | pass→pass | 7,074 | 3,786 | -46% | 1 | 1 | 0% | 1,486 | 1,761 | +19% | 0 | 0 | — |
case-22 | pass→pass | 6,666 | 4,254 | -36% | 1 | 1 | 0% | 1,032 | 1,765 | +71% | 0 | 0 | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 22 cases were attempted. The headline lift of +18 percentage points is the difference between those two pass rates over the 22 comparable cases.
Without the skill loaded, the model failed this case. With it loaded, the same prompt on the same model passed. This is one improved case from the latest verified run; every case, including any that regressed, is in the table above.
Other measured skills in the registry, with their headline benchmark lift.