Сервіси — це ті частини твого коду, де міститься бізнес-логіка. Якщо контролери — це фасад додатку, то сервіси — це свого роду "мізки". Помилки в сервісах можуть призвести до некоректного виконання бізнес-процесів.
Тестування сервісів допомагає:
- Перевірити, що логіка додатку працює так, як задумано.
- Переконатися, що всі виклики методів і взаємодії із залежностями виконуються коректно.
- Виявити баги ще до збірки й деплоя додатку.
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 допомагає переконатися в коректності запитів і роботи з даними.
Ти успішно підготував свою кодову базу до реальних задач, і баги більше не зможуть ховатися в твоєму додатку! Тепер можна рухатись далі до більш складних сценаріїв тестування, про які ти дізнаєшся в наступних лекціях.
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ