Сервисы — это те самые участки вашего кода, где содержится бизнес-логика. Если контроллеры — это фасад вашего приложения, то сервисы представляют собой "мозг". Ошибки в сервисах могут привести к некорректному выполнению бизнес-процессов.
Тестирование сервисов помогает:
- Проверить, что логика приложения работает так, как задумано.
- Убедиться, что все вызовы методов и взаимодействия с зависимостями выполняются корректно.
- Выявить баги ещё до сборки и деплоя приложения.
Mockito как ваш лучший друг
Для тестирования сервисов мы будем активно использовать Mockito. Почему? Потому что нам нужно протестировать именно бизнес-логику, изолировав её от всех внешних зависимостей, таких как базы данных или внешние API.
Основные шаги тестирования сервисов
- Создать моки для всех зависимостей сервиса (репозитории, другие сервисы и т.д.).
- Настроить поведение моков используя
when()иthenReturn(). - Проверить, что методы сервиса вызывают ожидаемые действия и возвращают корректные результаты.
- Опционально убедиться, что зависимости вызываются нужное количество раз (или не вызываются вовсе), используя методы, такие как
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);
}
Теперь мы уверены, что наш сервис работает корректно и реагирует на все возможные сценарии.
Введение в тестирование репозиториев
Репозитории — это мост между вашим приложением и базой данных. Поэтому важно убедиться, что они правильно взаимодействуют с базой данных, выполняя корректные запросы и обработки.
Как тестировать репозитории?
- Использовать встроенную базу данных, например, H2 (в памяти) для тестов.
- Аннотация
@DataJpaTestпомогает автоматически настроить тестовую среду для работы с JPA репозиториями. - Проверять, что результаты запросов соответствуют ожиданиям.
Практика: Тестирование репозиториев
Сущность
@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 базой помогает убедиться в корректности запросов и работы с данными.
Вы успешно подготовили свою кодовую базу к реальным задачам, и баги больше не смогут прятаться в вашем приложении! Теперь можно продолжать к более сложным сценариям тестирования, о которых вы узнаете в следующих лекциях.
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ