Mockito
Unit Testing
Mocking
Repository Pattern
Java Testing

Unit Tests How to Mock Repository Using Mockito

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

When unit testing a service, the repository is usually a dependency, not the thing you are trying to verify. Mocking that repository with Mockito keeps the test fast and focused, so the test can validate service behavior without starting Spring or connecting to a real database.

Define the Service Boundary Clearly

Suppose a service loads and creates users through a repository.

java
1import java.util.Optional;
2
3public interface UserRepository {
4    Optional<User> findByEmail(String email);
5    User save(User user);
6}
java
1public class User {
2    private final Long id;
3    private final String email;
4
5    public User(Long id, String email) {
6        this.id = id;
7        this.email = email;
8    }
9
10    public Long getId() {
11        return id;
12    }
13
14    public String getEmail() {
15        return email;
16    }
17}
java
1public class UserService {
2    private final UserRepository userRepository;
3
4    public UserService(UserRepository userRepository) {
5        this.userRepository = userRepository;
6    }
7
8    public User findUser(String email) {
9        return userRepository.findByEmail(email)
10            .orElseThrow(() -> new IllegalArgumentException("User not found"));
11    }
12
13    public User register(String email) {
14        User user = new User(null, email);
15        return userRepository.save(user);
16    }
17}

A unit test for UserService should treat the repository as replaceable infrastructure.

Mock the Repository with Mockito

JUnit 5 plus Mockito's extension support is a clean setup.

java
1import static org.junit.jupiter.api.Assertions.assertEquals;
2import static org.junit.jupiter.api.Assertions.assertThrows;
3import static org.mockito.ArgumentMatchers.any;
4import static org.mockito.Mockito.verify;
5import static org.mockito.Mockito.when;
6
7import java.util.Optional;
8
9import org.junit.jupiter.api.Test;
10import org.junit.jupiter.api.extension.ExtendWith;
11import org.mockito.InjectMocks;
12import org.mockito.Mock;
13import org.mockito.junit.jupiter.MockitoExtension;
14
15@ExtendWith(MockitoExtension.class)
16class UserServiceTest {
17
18    @Mock
19    private UserRepository userRepository;
20
21    @InjectMocks
22    private UserService userService;
23
24    @Test
25    void findUser_returnsUserWhenRepositoryFindsMatch() {
26        User user = new User(1L, "[email protected]");
27        when(userRepository.findByEmail("[email protected]"))
28            .thenReturn(Optional.of(user));
29
30        User result = userService.findUser("[email protected]");
31
32        assertEquals("[email protected]", result.getEmail());
33        verify(userRepository).findByEmail("[email protected]");
34    }
35
36    @Test
37    void findUser_throwsWhenRepositoryReturnsEmpty() {
38        when(userRepository.findByEmail("[email protected]"))
39            .thenReturn(Optional.empty());
40
41        assertThrows(IllegalArgumentException.class,
42            () -> userService.findUser("[email protected]"));
43    }
44}

This isolates the service without loading any persistence infrastructure.

Stub Only What the Scenario Needs

A good Mockito test stubs the repository methods needed for the behavior under test and then asserts the service outcome. That usually means verifying:

  • the returned value
  • the thrown exception
  • the important repository interaction

It does not mean verifying every internal detail of the implementation. Over-verification makes tests brittle.

Verify Saved Arguments When It Matters

If you need to inspect what the service sent to the repository, use ArgumentCaptor.

java
1import static org.junit.jupiter.api.Assertions.assertEquals;
2import static org.mockito.Mockito.verify;
3import static org.mockito.Mockito.when;
4
5import org.mockito.ArgumentCaptor;
6
7@Test
8void register_savesNewUser() {
9    when(userRepository.save(any(User.class)))
10        .thenAnswer(invocation -> {
11            User incoming = invocation.getArgument(0);
12            return new User(99L, incoming.getEmail());
13        });
14
15    User saved = userService.register("[email protected]");
16
17    ArgumentCaptor<User> captor = ArgumentCaptor.forClass(User.class);
18    verify(userRepository).save(captor.capture());
19
20    assertEquals("[email protected]", captor.getValue().getEmail());
21    assertEquals(99L, saved.getId());
22}

This is much cleaner than involving a database just to inspect one saved object.

Keep Unit Tests Separate from Repository Integration Tests

If you need to verify repository queries, mappings, or persistence behavior, write integration tests against the real data layer. A Mockito unit test should focus on the service's business decisions, not on whether a database query really works.

That separation keeps unit tests fast and keeps integration tests honest.

Common Pitfalls

A common mistake is loading the full Spring context for a simple service unit test. That makes the test slower and less focused than necessary.

Another is forgetting to stub a repository method. Mockito then returns a default value such as null or an empty optional-like result, which can make failures harder to read.

Developers also sometimes mock the service itself instead of the repository. That proves nothing about the actual business logic.

Summary

  • Mock the repository so the service can be tested without a database.
  • Use @Mock for dependencies and @InjectMocks for the service under test.
  • Stub only the repository behavior needed for the test scenario.
  • Verify meaningful outcomes and interactions, not every implementation detail.
  • Use ArgumentCaptor when you need to inspect what the service passed to the repository.

Course illustration
Course illustration

All Rights Reserved.