Gymterview
junior

Как тестировать сервисный слой с помощью MockitoExtension?

Для юнит-тестирования сервисного слоя не нужен контекст Spring. Используется @ExtendWith(MockitoExtension.class) совместно с @Mock и @InjectMocks.

  • @Mock — создаёт мок-объект для зависимости.
  • @InjectMocks — создаёт экземпляр тестируемого класса и внедряет в него все моки.
Полный пример теста сервиса
@ExtendWith(MockitoExtension.class)
class UserServiceTest {

    @Mock
    private UserRepository userRepository;

    @Mock
    private EmailService emailService;

    @Mock
    private PasswordEncoder passwordEncoder;

    @InjectMocks
    private UserServiceImpl userService;

    @Test
    void shouldCreateUserSuccessfully() {
        // Arrange
        UserDto dto = new UserDto("Иван", "ivan@example.com", "password");
        when(passwordEncoder.encode("password")).thenReturn("encodedPassword");
        when(userRepository.save(any(User.class))).thenAnswer(invocation -> {
            User user = invocation.getArgument(0);
            user.setId(1L);
            return user;
        });

        // Act
        User result = userService.create(dto);

        // Assert
        assertNotNull(result.getId());
        assertEquals("Иван", result.getName());
        assertEquals("encodedPassword", result.getPassword());

        verify(userRepository).save(any(User.class));
        verify(emailService).sendWelcomeEmail("ivan@example.com");
    }

    @Test
    void shouldThrowExceptionWhenUserNotFound() {
        when(userRepository.findById(999L)).thenReturn(Optional.empty());

        assertThrows(UserNotFoundException.class,
            () -> userService.findById(999L));

        verify(userRepository).findById(999L);
        verifyNoInteractions(emailService);
    }

    @Test
    void shouldUpdateUserName() {
        User existing = new User(1L, "Иван", "ivan@example.com");
        when(userRepository.findById(1L)).thenReturn(Optional.of(existing));
        when(userRepository.save(any(User.class))).thenReturn(existing);

        User result = userService.updateName(1L, "Пётр");

        assertEquals("Пётр", result.getName());

        ArgumentCaptor<User> captor = ArgumentCaptor.forClass(User.class);
        verify(userRepository).save(captor.capture());
        assertEquals("Пётр", captor.getValue().getName());
    }
}

Преимущества подхода

  • Быстрота — не поднимается контекст Spring
  • Изоляция — тестируется только логика сервиса
  • Детерминированность — нет зависимости от базы данных, сети и т.д.

На собеседовании: интервьюер хочет убедиться, что вы понимаете разницу между @MockBean (Spring-контекст) и @Mock + @InjectMocks (чистый Mockito). Частая ошибка — использовать @SpringBootTest для юнит-тестов сервисов вместо MockitoExtension.