JavaRush /Курсы /Модуль 5. Spring /Лекция 135: Тестирование сервисов и репозиториев

Лекция 135: Тестирование сервисов и репозиториев

Модуль 5. Spring
14 уровень , 4 лекция
Открыта

Сервисы — это те самые участки вашего кода, где содержится бизнес-логика. Если контроллеры — это фасад вашего приложения, то сервисы представляют собой "мозг". Ошибки в сервисах могут привести к некорректному выполнению бизнес-процессов.

Тестирование сервисов помогает:

  • Проверить, что логика приложения работает так, как задумано.
  • Убедиться, что все вызовы методов и взаимодействия с зависимостями выполняются корректно.
  • Выявить баги ещё до сборки и деплоя приложения.

Mockito как ваш лучший друг

Для тестирования сервисов мы будем активно использовать Mockito. Почему? Потому что нам нужно протестировать именно бизнес-логику, изолировав её от всех внешних зависимостей, таких как базы данных или внешние API.

Основные шаги тестирования сервисов

  1. Создать моки для всех зависимостей сервиса (репозитории, другие сервисы и т.д.).
  2. Настроить поведение моков используя when() и thenReturn().
  3. Проверить, что методы сервиса вызывают ожидаемые действия и возвращают корректные результаты.
  4. Опционально убедиться, что зависимости вызываются нужное количество раз (или не вызываются вовсе), используя методы, такие как verify().

Практика: тестирование сервисов

Возьмём пример. У нас есть сервис, который рассчитывает скидки для пользователей. Он обращается к репозиторию для получения информации о пользователе и другой логике приложения.

Сервис


@Service
public class DiscountService {

    private final UserRepository userRepository;

    public DiscountService(UserRepository userRepository) {
        this.userRepository = userRepository;
    }

    public double calculateDiscount(Long userId) {
        User user = userRepository.findById(userId)
                .orElseThrow(() -> new IllegalArgumentException("User not found"));

        if (user.isPremium()) {
            return 20.0; // 20% скидка для премиум пользователей
        }
        return 5.0; // 5% скидка для всех остальных
    }
}

Тестирование

1. Создадим класс теста и настроим моки:


@ExtendWith(MockitoExtension.class)
class DiscountServiceTest {

    @Mock
    private UserRepository userRepository;

    @InjectMocks
    private DiscountService discountService;

    // Тестовые данные
    private final User premiumUser = new User(1L, "PremiumUser", true);
    private final User regularUser = new User(2L, "RegularUser", false);

    // ...
}

2. Тестируем метод calculateDiscount() для премиум пользователей:


@Test
void shouldReturn20PercentDiscountForPremiumUsers() {
    // Настраиваем мок
    when(userRepository.findById(1L)).thenReturn(Optional.of(premiumUser));

    // Действие
    double discount = discountService.calculateDiscount(1L);

    // Проверка
    assertEquals(20.0, discount);
    verify(userRepository, times(1)).findById(1L); // Убедимся, что репозиторий вызывался ровно 1 раз
}

3. Тестируем метод calculateDiscount() для обычных пользователей:


@Test
void shouldReturn5PercentDiscountForRegularUsers() {
    // Настраиваем мок
    when(userRepository.findById(2L)).thenReturn(Optional.of(regularUser));

    // Действие
    double discount = discountService.calculateDiscount(2L);

    // Проверка
    assertEquals(5.0, discount);
    verify(userRepository, times(1)).findById(2L);
}

4. Обрабатываем случай, когда пользователь не найден:


@Test
void shouldThrowExceptionWhenUserNotFound() {
    // Настраиваем мок
    when(userRepository.findById(anyLong())).thenReturn(Optional.empty());

    // Проверка на исключение
    Exception exception = assertThrows(IllegalArgumentException.class,
        () -> discountService.calculateDiscount(99L));

    assertEquals("User not found", exception.getMessage());
    verify(userRepository, times(1)).findById(99L);
}

Теперь мы уверены, что наш сервис работает корректно и реагирует на все возможные сценарии.


Введение в тестирование репозиториев

Репозитории — это мост между вашим приложением и базой данных. Поэтому важно убедиться, что они правильно взаимодействуют с базой данных, выполняя корректные запросы и обработки.

Как тестировать репозитории?

  1. Использовать встроенную базу данных, например, H2 (в памяти) для тестов.
  2. Аннотация @DataJpaTest помогает автоматически настроить тестовую среду для работы с JPA репозиториями.
  3. Проверять, что результаты запросов соответствуют ожиданиям.

Практика: Тестирование репозиториев

Сущность


@Entity
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;
    private boolean isPremium;

    // Конструкторы, геттеры и сеттеры
}

Репозиторий


@Repository
public interface UserRepository extends JpaRepository<User, Long> {
    List<User> findAllByIsPremium(boolean isPremium);
}

Тестирование

1. Создадим тестовый класс:


@DataJpaTest
class UserRepositoryTest {

    @Autowired
    private TestEntityManager entityManager;

    @Autowired
    private UserRepository userRepository;

    // ...
}

2. Тестируем метод findAllByIsPremium:


@Test
void shouldFindAllPremiumUsers() {
    // Подготовка тестовых данных
    User premiumUser = new User(null, "PremiumUser", true);
    User regularUser = new User(null, "RegularUser", false);

    entityManager.persist(premiumUser);
    entityManager.persist(regularUser);
    entityManager.flush();

    // Действие
    List<User> result = userRepository.findAllByIsPremium(true);

    // Проверка
    assertEquals(1, result.size());
    assertEquals("PremiumUser", result.get(0).getName());
}

3. Тестируем стандартные методы JPA (например, findById):


@Test
void shouldFindUserById() {
    // Подготовка данных
    User user = new User(null, "TestUser", false);
    User savedUser = entityManager.persistFlushFind(user);

    // Проверка
    Optional<User> result = userRepository.findById(savedUser.getId());
    assertTrue(result.isPresent());
    assertEquals("TestUser", result.get().getName());
}

Итоги

Тестирование сервисов с использованием Mockito изолирует логику от внешних зависимостей, позволяя сосредоточиться только на проверке бизнес-логики. Тогда как тестирование репозиториев с аннотацией @DataJpaTest и H2 базой помогает убедиться в корректности запросов и работы с данными.

Вы успешно подготовили свою кодовую базу к реальным задачам, и баги больше не смогут прятаться в вашем приложении! Теперь можно продолжать к более сложным сценариям тестирования, о которых вы узнаете в следующих лекциях.

Комментарии (1)
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ
Артём Уровень 112
15 сентября 2025
Примеры сущности и репозитория очень неплохо было бы в начало лекции перенести. И метод findAllByIsPremium нужно поправить. Опять поломались сигнатура метода и форматирование.