CodeGym /Courses /Module 5. Spring /Lecture 135: Testing services and repositories

Lecture 135: Testing services and repositories

Module 5. Spring
Level 9 , Lesson 4
Available

Services are the parts of your code that contain business logic. If controllers are the facade of your app, services are the "brain". Bugs in services can lead to incorrect execution of business processes.

Testing services helps you:

  • Verify that the application logic works the way it's supposed to.
  • Make sure that all method calls and interactions with dependencies are performed correctly.
  • Catch bugs before the app is built and deployed.

Mockito as your best friend

For testing services we'll be using Mockito a lot. Why? Because we want to test business logic in isolation from external dependencies, like databases or external APIs.

Main steps for testing services

  1. Create mocks for all service dependencies (repositories, other services, etc.).
  2. Configure mock behavior using when() and thenReturn().
  3. Verify that service methods trigger the expected actions and return correct results.
  4. Optionally ensure dependencies are called the expected number of times (or not called), using methods like verify().

Practical: testing services

Let's take an example. We have a service that calculates discounts for users. It queries a repository to get user info and contains other app logic.

Service


@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% discount for premium users
        }
        return 5.0; // 5% discount for everyone else
    }
}

Testing

1. Create the test class and set up mocks:


@ExtendWith(MockitoExtension.class)
class DiscountServiceTest {

    @Mock
    private UserRepository userRepository;

    @InjectMocks
    private DiscountService discountService;

    // Test data
    private final User premiumUser = new User(1L, "PremiumUser", true);
    private final User regularUser = new User(2L, "RegularUser", false);

    // ...
}

2. Test calculateDiscount() for premium users:


@Test
void shouldReturn20PercentDiscountForPremiumUsers() {
    // Set up the mock
    when(userRepository.findById(1L)).thenReturn(Optional.of(premiumUser));

    // Action
    double discount = discountService.calculateDiscount(1L);

    // Assertion
    assertEquals(20.0, discount);
    verify(userRepository, times(1)).findById(1L); // Make sure the repository was called exactly once
}

3. Test calculateDiscount() for regular users:


@Test
void shouldReturn5PercentDiscountForRegularUsers() {
    // Set up the mock
    when(userRepository.findById(2L)).thenReturn(Optional.of(regularUser));

    // Action
    double discount = discountService.calculateDiscount(2L);

    // Assertion
    assertEquals(5.0, discount);
    verify(userRepository, times(1)).findById(2L);
}

4. Handle the case when the user is not found:


@Test
void shouldThrowExceptionWhenUserNotFound() {
    // Set up the mock
    when(userRepository.findById(anyLong())).thenReturn(Optional.empty());

    // Check for exception
    Exception exception = assertThrows(IllegalArgumentException.class,
        () -> discountService.calculateDiscount(99L));

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

Now we're confident that our service works correctly and handles all possible scenarios.


Introduction to testing repositories

Repositories are the bridge between your app and the database. So it's important to make sure they interact with the database properly, running correct queries and handling results.

How to test repositories?

  1. Use an embedded database, for example H2 (in-memory) for tests.
  2. The @DataJpaTest annotation helps auto-configure a test environment for JPA repositories.
  3. Verify that query results match expectations.

Practical: Testing repositories

Entity


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

    // Constructors, getters and setters
}

Repository


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

Testing

1. Create the test class:


@DataJpaTest
class UserRepositoryTest {

    @Autowired
    private TestEntityManager entityManager;

    @Autowired
    private UserRepository userRepository;

    // ...
}

2. Test the findAllByIsPremium method:


@Test
void shouldFindAllPremiumUsers() {
    // Preparing test data
    User premiumUser = new User(null, "PremiumUser", true);
    User regularUser = new User(null, "RegularUser", false);

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

    // Action
    List<User> result = userRepository.findAllByIsPremium(true);

    // Assertion
    assertEquals(1, result.size());
    assertEquals("PremiumUser", result.get(0).getName());
}

3. Test standard JPA methods (for example, findById):


@Test
void shouldFindUserById() {
    // Preparing data
    User user = new User(null, "TestUser", false);
    User savedUser = entityManager.persistFlushFind(user);

    // Assertion
    Optional<User> result = userRepository.findById(savedUser.getId());
    assertTrue(result.isPresent());
    assertEquals("TestUser", result.get().getName());
}

Summary

Testing services with Mockito isolates your logic from external dependencies, letting you focus solely on verifying business logic. Testing repositories with @DataJpaTest and an H2 database helps ensure that your queries and data handling are correct.

You've successfully prepared your codebase for real-world tasks, and bugs won't be able to hide in your app anymore! Now you can move on to more advanced testing scenarios, which you'll learn about in the next lectures.

Comments
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION