tessl install github:giuseppe-trisciuoglio/developer-kit --skill unit-test-service-layergithub.com/giuseppe-trisciuoglio/developer-kit
Unit tests for service layer with Mockito. Test business logic in isolation by mocking dependencies. Use when validating service behaviors and business logic without database or external services.
Review Score
78%
Validation Score
12/16
Implementation Score
65%
Activation Score
90%
Test @Service annotated classes by mocking all injected dependencies. Focus on business logic validation without starting the Spring container.
Use this skill when:
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-junit-jupiter</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>
<scope>test</scope>
</dependency>dependencies {
testImplementation("org.junit.jupiter:junit-jupiter")
testImplementation("org.mockito:mockito-core")
testImplementation("org.mockito:mockito-junit-jupiter")
testImplementation("org.assertj:assertj-core")
}import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import static org.mockito.Mockito.*;
import static org.assertj.core.api.Assertions.*;
@ExtendWith(MockitoExtension.class)
class UserServiceTest {
@Mock
private UserRepository userRepository;
@InjectMocks
private UserService userService;
@Test
void shouldReturnAllUsers() {
// Arrange
List<User> expectedUsers = List.of(
new User(1L, "Alice"),
new User(2L, "Bob")
);
when(userRepository.findAll()).thenReturn(expectedUsers);
// Act
List<User> result = userService.getAllUsers();
// Assert
assertThat(result).hasSize(2);
assertThat(result).containsExactly(
new User(1L, "Alice"),
new User(2L, "Bob")
);
verify(userRepository, times(1)).findAll();
}
}@ExtendWith(MockitoExtension.class)
class UserEnrichmentServiceTest {
@Mock
private UserRepository userRepository;
@Mock
private EmailService emailService;
@Mock
private AnalyticsClient analyticsClient;
@InjectMocks
private UserEnrichmentService enrichmentService;
@Test
void shouldCreateUserAndSendWelcomeEmail() {
User newUser = new User(1L, "Alice", "alice@example.com");
when(userRepository.save(any(User.class))).thenReturn(newUser);
doNothing().when(emailService).sendWelcomeEmail(newUser.getEmail());
User result = enrichmentService.registerNewUser("Alice", "alice@example.com");
assertThat(result.getId()).isEqualTo(1L);
assertThat(result.getName()).isEqualTo("Alice");
verify(userRepository).save(any(User.class));
verify(emailService).sendWelcomeEmail("alice@example.com");
verify(analyticsClient, never()).trackUserRegistration(any());
}
}@Test
void shouldThrowExceptionWhenUserNotFound() {
when(userRepository.findById(999L))
.thenThrow(new UserNotFoundException("User not found"));
assertThatThrownBy(() -> userService.getUserDetails(999L))
.isInstanceOf(UserNotFoundException.class)
.hasMessageContaining("User not found");
verify(userRepository).findById(999L);
}
@Test
void shouldRethrowRepositoryException() {
when(userRepository.findAll())
.thenThrow(new DataAccessException("Database connection failed"));
assertThatThrownBy(() -> userService.getAllUsers())
.isInstanceOf(DataAccessException.class)
.hasMessageContaining("Database connection failed");
}@Test
void shouldTransferMoneyBetweenAccounts() {
Account fromAccount = new Account(1L, 1000.0);
Account toAccount = new Account(2L, 500.0);
when(accountRepository.findById(1L)).thenReturn(Optional.of(fromAccount));
when(accountRepository.findById(2L)).thenReturn(Optional.of(toAccount));
when(accountRepository.save(any(Account.class)))
.thenAnswer(invocation -> invocation.getArgument(0));
moneyTransferService.transfer(1L, 2L, 200.0);
// Verify both accounts were updated
verify(accountRepository, times(2)).save(any(Account.class));
assertThat(fromAccount.getBalance()).isEqualTo(800.0);
assertThat(toAccount.getBalance()).isEqualTo(700.0);
}import org.mockito.ArgumentCaptor;
@Test
void shouldCaptureUserDataWhenSaving() {
ArgumentCaptor<User> userCaptor = ArgumentCaptor.forClass(User.class);
when(userRepository.save(any(User.class)))
.thenAnswer(invocation -> invocation.getArgument(0));
userService.createUser("Alice", "alice@example.com");
verify(userRepository).save(userCaptor.capture());
User capturedUser = userCaptor.getValue();
assertThat(capturedUser.getName()).isEqualTo("Alice");
assertThat(capturedUser.getEmail()).isEqualTo("alice@example.com");
}
@Test
void shouldCaptureMultipleArgumentsAcrossMultipleCalls() {
ArgumentCaptor<User> userCaptor = ArgumentCaptor.forClass(User.class);
userService.createUser("Alice", "alice@example.com");
userService.createUser("Bob", "bob@example.com");
verify(userRepository, times(2)).save(userCaptor.capture());
List<User> capturedUsers = userCaptor.getAllValues();
assertThat(capturedUsers).hasSize(2);
assertThat(capturedUsers.get(0).getName()).isEqualTo("Alice");
assertThat(capturedUsers.get(1).getName()).isEqualTo("Bob");
}import org.mockito.InOrder;
@Test
void shouldCallMethodsInCorrectOrder() {
InOrder inOrder = inOrder(userRepository, emailService);
userService.registerNewUser("Alice", "alice@example.com");
inOrder.verify(userRepository).save(any(User.class));
inOrder.verify(emailService).sendWelcomeEmail(any());
}
@Test
void shouldCallMethodExactlyOnce() {
userService.getUserDetails(1L);
verify(userRepository, times(1)).findById(1L);
verify(userRepository, never()).findAll();
}@Test
void shouldReturnCompletableFutureWhenFetchingAsyncData() {
List<User> users = List.of(new User(1L, "Alice"));
when(userRepository.findAllAsync())
.thenReturn(CompletableFuture.completedFuture(users));
CompletableFuture<List<User>> result = userService.getAllUsersAsync();
assertThat(result).isCompletedWithValue(users);
}expectedUser, actualUser, captorPartial Mock with Spy:
@Spy
@InjectMocks
private UserService userService; // Real instance, but can stub some methods
@Test
void shouldUseRealMethodButMockDependency() {
when(userRepository.findById(any())).thenReturn(Optional.of(new User()));
// Calls real userService methods but userRepository is mocked
}Constructor Injection for Testing:
// In your service (production code)
public class UserService {
private final UserRepository userRepository;
public UserService(UserRepository userRepository) {
this.repository = userRepository;
}
}
// In your test - can inject mocks directly
@Test
void test() {
UserRepository mockRepo = mock(UserRepository.class);
UserService service = new UserService(mockRepo);
}UnfinishedStubbingException: Ensure all when() calls are completed with thenReturn(), thenThrow(), or thenAnswer().
UnnecessaryStubbingException: Remove unused stub definitions. Use @ExtendWith(MockitoExtension.class) with MockitoExtension.LENIENT if you intentionally have unused stubs.
NullPointerException in test: Verify @InjectMocks correctly injects all mocked dependencies into the service constructor.