How do I test repository-like classes without a real database?

You can test repository-like classes without a real database by replacing the database dependency with a fake, mock, or in-memory implementation, depending on what you want to verify.

1. Use mocks for unit tests

If your class depends on a repository interface, mock it and verify behavior without touching a database.

Example with JUnit 5 and Mockito:

import org.junit.jupiter.api.Test;
import org.mockito.Mockito;

import java.util.Optional;

import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.when;

class UserServiceTest {

    @Test
    void returnsUserName() {
        UserRepository userRepository = Mockito.mock(UserRepository.class);

        when(userRepository.findById(1L))
                .thenReturn(Optional.of(new User(1L, "Alice")));

        UserService service = new UserService(userRepository);

        String name = service.getUserName(1L);

        assertThat(name).isEqualTo("Alice");
    }
}

This is best when testing service logic, not repository implementation details.


2. Use a fake in-memory repository

If you have repository-like classes that are simple abstractions, create an in-memory fake.

import java.util.HashMap;
import java.util.Map;
import java.util.Optional;

class InMemoryUserRepository implements UserRepository {

    private final Map<Long, User> users = new HashMap<>();

    @Override
    public Optional<User> findById(Long id) {
        return Optional.ofNullable(users.get(id));
    }

    @Override
    public User save(User user) {
        users.put(user.id(), user);
        return user;
    }
}

Then use it in tests:

import org.junit.jupiter.api.Test;

import static org.assertj.core.api.Assertions.assertThat;

class UserServiceTest {

    @Test
    void savesAndLoadsUser() {
        UserRepository repository = new InMemoryUserRepository();
        UserService service = new UserService(repository);

        service.createUser(1L, "Alice");

        assertThat(service.getUserName(1L)).isEqualTo("Alice");
    }
}

This is useful when you want tests that are more realistic than mocks but still fast.


3. Use Spring @MockBean / @MockitoBean in Spring tests

For Spring MVC or service-layer tests, replace a repository bean with a mock.

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.bean.override.mockito.MockitoBean;
import org.springframework.boot.test.context.SpringBootTest;

import java.util.Optional;

import static org.mockito.Mockito.when;
import static org.assertj.core.api.Assertions.assertThat;

@SpringBootTest
class UserServiceSpringTest {

    @MockitoBean
    private UserRepository userRepository;

    @Autowired
    private UserService userService;

    @Test
    void returnsUserName() {
        when(userRepository.findById(1L))
                .thenReturn(Optional.of(new User(1L, "Alice")));

        assertThat(userService.getUserName(1L)).isEqualTo("Alice");
    }
}

Use this when you want Spring wiring but not database access.


4. Use @DataJpaTest with an embedded database

If you are testing a Spring Data JPA repository itself, mocks are usually not enough. You need to verify queries, mappings, transactions, and entity relationships.

For that, use an embedded database such as H2:

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;

import static org.assertj.core.api.Assertions.assertThat;

@DataJpaTest
class UserRepositoryTest {

    @Autowired
    private UserRepository userRepository;

    @Test
    void findsByEmail() {
        User user = new User();
        user.setName("Alice");
        user.setEmail("[email protected]");

        userRepository.save(user);

        User found = userRepository.findByEmail("[email protected]").orElseThrow();

        assertThat(found.getName()).isEqualTo("Alice");
    }
}

This does use a database, but not a “real” external one. It is fast and isolated.


5. Use Testcontainers for production-like repository tests

If your repository uses database-specific features, H2 may behave differently from PostgreSQL, MySQL, Oracle, etc.

In that case, use Testcontainers:

import org.junit.jupiter.api.Test;
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
import org.springframework.boot.testcontainers.service.connection.ServiceConnection;
import org.springframework.test.context.DynamicPropertyRegistry;
import org.testcontainers.containers.PostgreSQLContainer;
import org.testcontainers.junit.jupiter.Container;
import org.testcontainers.junit.jupiter.Testcontainers;

@DataJpaTest
@Testcontainers
class UserRepositoryTest {

    @Container
    @ServiceConnection
    static PostgreSQLContainer<?> postgres =
            new PostgreSQLContainer<>("postgres:16");

    @Test
    void testRepository() {
        // repository test here
    }
}

This is not a local “real database” you manage manually, but it gives you much higher confidence.


Recommended approach

What you are testing Recommended technique
Service using a repository Mock repository
Business logic with persistence-like behavior Fake in-memory repository
Spring wiring without DB @MockitoBean
JPA mappings and queries @DataJpaTest
DB-specific SQL/features Testcontainers

Rule of thumb

Do not unit test Spring Data JPA repositories by mocking JPA internals like EntityManager unless your repository has significant custom logic.

Instead:

  • Mock repositories when testing services.
  • Use fakes when testing domain/application logic.
  • Use @DataJpaTest or Testcontainers when testing actual persistence behavior.

How do I test service classes with JUnit and Mockito?

Testing Service Classes with JUnit and Mockito

Service classes are usually where your business logic lives. They often depend on repositories, clients, mappers, validators, or other services.

When unit testing a service, the goal is usually:

  • test the service logic itself
  • mock external dependencies
  • avoid starting the Spring container unless necessary
  • verify returned values, exceptions, and interactions

For most service unit tests, you can use JUnit 5 with Mockito.


1. Example Service Class

Suppose you have a service that creates and retrieves users.

package com.example.user;

import java.util.Optional;

public class UserService {

    private final UserRepository userRepository;
    private final EmailValidator emailValidator;

    public UserService(UserRepository userRepository, EmailValidator emailValidator) {
        this.userRepository = userRepository;
        this.emailValidator = emailValidator;
    }

    public User createUser(String name, String email) {
        if (!emailValidator.isValid(email)) {
            throw new IllegalArgumentException("Invalid email address");
        }

        if (userRepository.existsByEmail(email)) {
            throw new IllegalStateException("Email already exists");
        }

        User user = new User(null, name, email);
        return userRepository.save(user);
    }

    public User getUserById(Long id) {
        return userRepository.findById(id)
                .orElseThrow(() -> new UserNotFoundException("User not found: " + id));
    }
}

Supporting classes might look like this:

package com.example.user;

public record User(Long id, String name, String email) {
}
package com.example.user;

import java.util.Optional;

public interface UserRepository {
    boolean existsByEmail(String email);

    User save(User user);

    Optional<User> findById(Long id);
}
package com.example.user;

public interface EmailValidator {
    boolean isValid(String email);
}
package com.example.user;

public class UserNotFoundException extends RuntimeException {
    public UserNotFoundException(String message) {
        super(message);
    }
}

2. Add JUnit and Mockito Dependencies

Maven

<dependencies>
    <!-- JUnit 5 -->
    <dependency>
        <groupId>org.junit.jupiter</groupId>
        <artifactId>junit-jupiter</artifactId>
        <version>5.11.4</version>
        <scope>test</scope>
    </dependency>

    <!-- Mockito + JUnit 5 integration -->
    <dependency>
        <groupId>org.mockito</groupId>
        <artifactId>mockito-junit-jupiter</artifactId>
        <version>5.15.2</version>
        <scope>test</scope>
    </dependency>
</dependencies>

If you use Maven, also make sure Surefire supports JUnit 5:

<build>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-surefire-plugin</artifactId>
            <version>3.5.2</version>
        </plugin>
    </plugins>
</build>

Gradle

dependencies {
    testImplementation 'org.junit.jupiter:junit-jupiter:5.11.4'
    testImplementation 'org.mockito:mockito-junit-jupiter:5.15.2'
}

test {
    useJUnitPlatform()
}

3. Basic Service Test with Mockito

Use @ExtendWith(MockitoExtension.class) to enable Mockito in JUnit 5.

package com.example.user;

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;

import java.util.Optional;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

@ExtendWith(MockitoExtension.class)
class UserServiceTest {

    @Mock
    private UserRepository userRepository;

    @Mock
    private EmailValidator emailValidator;

    @InjectMocks
    private UserService userService;

    @Test
    void createUser_WithValidEmailAndNewEmail_ReturnsSavedUser() {
        // Arrange
        String name = "Alice";
        String email = "[email protected]";

        User savedUser = new User(1L, name, email);

        when(emailValidator.isValid(email)).thenReturn(true);
        when(userRepository.existsByEmail(email)).thenReturn(false);
        when(userRepository.save(any(User.class))).thenReturn(savedUser);

        // Act
        User result = userService.createUser(name, email);

        // Assert
        assertEquals(1L, result.id());
        assertEquals("Alice", result.name());
        assertEquals("[email protected]", result.email());

        verify(emailValidator).isValid(email);
        verify(userRepository).existsByEmail(email);
        verify(userRepository).save(any(User.class));
    }

    @Test
    void createUser_WithInvalidEmail_ThrowsException() {
        // Arrange
        String email = "invalid-email";

        when(emailValidator.isValid(email)).thenReturn(false);

        // Act
        IllegalArgumentException exception = assertThrows(
                IllegalArgumentException.class,
                () -> userService.createUser("Alice", email)
        );

        // Assert
        assertEquals("Invalid email address", exception.getMessage());

        verify(emailValidator).isValid(email);
        verify(userRepository, never()).existsByEmail(email);
        verify(userRepository, never()).save(any(User.class));
    }

    @Test
    void createUser_WithExistingEmail_ThrowsException() {
        // Arrange
        String email = "[email protected]";

        when(emailValidator.isValid(email)).thenReturn(true);
        when(userRepository.existsByEmail(email)).thenReturn(true);

        // Act
        IllegalStateException exception = assertThrows(
                IllegalStateException.class,
                () -> userService.createUser("Alice", email)
        );

        // Assert
        assertEquals("Email already exists", exception.getMessage());

        verify(emailValidator).isValid(email);
        verify(userRepository).existsByEmail(email);
        verify(userRepository, never()).save(any(User.class));
    }

    @Test
    void getUserById_WhenUserExists_ReturnsUser() {
        // Arrange
        User user = new User(1L, "Alice", "[email protected]");

        when(userRepository.findById(1L)).thenReturn(Optional.of(user));

        // Act
        User result = userService.getUserById(1L);

        // Assert
        assertEquals(user, result);

        verify(userRepository).findById(1L);
    }

    @Test
    void getUserById_WhenUserDoesNotExist_ThrowsException() {
        // Arrange
        when(userRepository.findById(99L)).thenReturn(Optional.empty());

        // Act
        UserNotFoundException exception = assertThrows(
                UserNotFoundException.class,
                () -> userService.getUserById(99L)
        );

        // Assert
        assertTrue(exception.getMessage().contains("99"));

        verify(userRepository).findById(99L);
    }
}

4. What @Mock and @InjectMocks Do

@Mock

Creates fake versions of dependencies.

@Mock
private UserRepository userRepository;

This means you control what the repository returns:

when(userRepository.findById(1L))
        .thenReturn(Optional.of(user));

@InjectMocks

Creates the service under test and injects the mocks into it.

@InjectMocks
private UserService userService;

Mockito will try constructor injection first, which works well if your service uses constructor injection.


5. Typical Test Structure

A clean service test usually follows Arrange, Act, Assert:

@Test
void methodName_StateUnderTest_ExpectedBehavior() {
    // Arrange
    when(repository.findById(1L)).thenReturn(Optional.of(entity));

    // Act
    Result result = service.method(1L);

    // Assert
    assertEquals(expected, result);
    verify(repository).findById(1L);
}

6. Testing Exceptions

Use assertThrows() when the service should reject invalid input or missing data.

@Test
void getUserById_WhenUserMissing_ThrowsException() {
    when(userRepository.findById(1L)).thenReturn(Optional.empty());

    UserNotFoundException exception = assertThrows(
            UserNotFoundException.class,
            () -> userService.getUserById(1L)
    );

    assertEquals("User not found: 1", exception.getMessage());
}

7. Verifying Repository Calls

Mockito can check whether a dependency method was called.

verify(userRepository).findById(1L);

You can also verify that something was not called:

verify(userRepository, never()).save(any(User.class));

This is useful when testing validation failures.


8. Capturing Arguments

Sometimes you need to inspect the object passed to a mocked dependency.

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.Mockito.verify;

@ExtendWith(MockitoExtension.class)
class UserServiceArgumentCaptorTest {

    @Mock
    private UserRepository userRepository;

    @Mock
    private EmailValidator emailValidator;

    @InjectMocks
    private UserService userService;

    @Captor
    private ArgumentCaptor<User> userCaptor;

    @Test
    void createUser_PassesCorrectUserToRepository() {
        // Arrange
        when(emailValidator.isValid("[email protected]")).thenReturn(true);
        when(userRepository.existsByEmail("[email protected]")).thenReturn(false);
        when(userRepository.save(any(User.class)))
                .thenAnswer(invocation -> invocation.getArgument(0));

        // Act
        userService.createUser("Alice", "[email protected]");

        // Assert
        verify(userRepository).save(userCaptor.capture());

        User capturedUser = userCaptor.getValue();

        assertEquals("Alice", capturedUser.name());
        assertEquals("[email protected]", capturedUser.email());
    }
}

Additional imports needed for this example:

import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.when;

9. When Should You Use @SpringBootTest?

For normal service unit tests, you usually do not need this:

@SpringBootTest

@SpringBootTest starts the Spring application context, which makes tests slower and more integration-style.

Use plain Mockito tests when you want to test only the service logic:

@ExtendWith(MockitoExtension.class)
class UserServiceTest {
}

Use @SpringBootTest when you want to test that Spring wiring, configuration, transactions, database integration, or multiple beans work together.


10. Service Test Checklist

When testing service classes:

  • Mock repositories and external clients.
  • Use the real service class.
  • Test successful paths.
  • Test validation failures.
  • Test missing data scenarios.
  • Test exception paths.
  • Verify important dependency calls.
  • Avoid testing getters, setters, or framework behavior.
  • Avoid starting Spring unless you need integration testing.
  • Prefer constructor injection in your service classes.

Summary

To test service classes with JUnit and Mockito:

  1. Add junit-jupiter and mockito-junit-jupiter.
  2. Annotate the test with @ExtendWith(MockitoExtension.class).
  3. Mock dependencies with @Mock.
  4. Create the service with @InjectMocks.
  5. Stub dependency behavior with when(...).thenReturn(...).
  6. Call the service method.
  7. Assert the result with JUnit assertions.
  8. Verify interactions with Mockito when useful.

For most service classes, this gives you fast, focused, and reliable unit tests.

How do I mock exceptions in JUnit tests?

In JUnit tests, you usually don’t “mock” exceptions directly. Instead, you either:

  1. Assert that real code throws an exception, or
  2. Configure a mock dependency to throw an exception.

1. Assert that code throws an exception

With JUnit 5, use assertThrows.

import org.junit.jupiter.api.Test;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;

class MyServiceTest {

    @Test
    void shouldThrowException() {
        IllegalArgumentException exception = assertThrows(
                IllegalArgumentException.class,
                () -> {
                    throw new IllegalArgumentException("Invalid input");
                }
        );

        assertEquals("Invalid input", exception.getMessage());
    }
}

assertThrows verifies that the code inside the lambda throws the expected exception type.

2. Mock a dependency to throw an exception with Mockito

If your class depends on another object, you can configure the mock to throw an exception.

import org.junit.jupiter.api.Test;
import org.mockito.Mockito;

import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.Mockito.when;

class UserServiceTest {

    @Test
    void shouldThrowWhenRepositoryFails() {
        UserRepository repository = Mockito.mock(UserRepository.class);

        when(repository.findById(1L))
                .thenThrow(new RuntimeException("Database error"));

        UserService service = new UserService(repository);

        assertThrows(
                RuntimeException.class,
                () -> service.getUser(1L)
        );
    }
}

3. Mock exceptions for void methods

For void methods, use doThrow.

import org.junit.jupiter.api.Test;
import org.mockito.Mockito;

import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.Mockito.doThrow;

class NotificationServiceTest {

    @Test
    void shouldThrowWhenEmailFails() {
        EmailClient emailClient = Mockito.mock(EmailClient.class);

        doThrow(new RuntimeException("Email failed"))
                .when(emailClient)
                .sendEmail("[email protected]");

        NotificationService service = new NotificationService(emailClient);

        assertThrows(
                RuntimeException.class,
                () -> service.notifyUser("[email protected]")
        );
    }
}

4. Check the exception message

You can capture the thrown exception and verify its message.

import org.junit.jupiter.api.Test;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;

class CalculatorTest {

    @Test
    void shouldThrowArithmeticException() {
        ArithmeticException exception = assertThrows(
                ArithmeticException.class,
                () -> {
                    int result = 10 / 0;
                }
        );

        assertEquals("/ by zero", exception.getMessage());
    }
}

5. JUnit 4 alternative

If you are using JUnit 4, you can use the expected attribute.

import org.junit.Test;

public class CalculatorTest {

    @Test(expected = ArithmeticException.class)
    public void shouldThrowArithmeticException() {
        int result = 10 / 0;
    }
}

However, in JUnit 4, ExpectedException or AssertJ is better if you need to check the message.

import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;

public class CalculatorTest {

    @Rule
    public ExpectedException exception = ExpectedException.none();

    @Test
    public void shouldThrowWithMessage() {
        exception.expect(IllegalArgumentException.class);
        exception.expectMessage("Invalid input");

        throw new IllegalArgumentException("Invalid input");
    }
}

Quick rule of thumb

  • Use assertThrows when testing your own method throws an exception.
  • Use when(...).thenThrow(...) for mocked methods that return a value.
  • Use doThrow(...).when(...) for mocked void methods.

For modern Java projects, prefer JUnit 5 + Mockito:

assertThrows(SomeException.class, () -> service.method());

How do I verify method calls with Mockito and JUnit?

To verify method calls with Mockito and JUnit, use Mockito’s verify() method. This lets you check whether a mocked dependency method was called, how many times it was called, and what arguments were passed.

1. Basic Example

Suppose you have a service that depends on a repository.

public interface UserRepository {
    void save(User user);
}
public class UserService {
    private final UserRepository userRepository;

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

    public void register(User user) {
        userRepository.save(user);
    }
}

You can verify that save() was called:

import org.junit.jupiter.api.Test;

import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;

class UserServiceTest {

    @Test
    void register_shouldSaveUser() {
        UserRepository userRepository = mock(UserRepository.class);
        UserService userService = new UserService(userRepository);

        User user = new User();

        userService.register(user);

        verify(userRepository).save(user);
    }
}

The important line is:

verify(userRepository).save(user);

This means: “After running the test, confirm that save(user) was called on userRepository.”

2. Verifying Number of Calls

By default, verify() expects the method to be called exactly once.

These two lines are equivalent:

verify(userRepository).save(user);
verify(userRepository, times(1)).save(user);

You can verify different call counts:

import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;

verify(userRepository, times(1)).save(user);
verify(userRepository, times(2)).save(user);
verify(userRepository, never()).delete(user);

Common options include:

verify(mock, times(1)).method();
verify(mock, never()).method();
verify(mock, atLeastOnce()).method();
verify(mock, atLeast(2)).method();
verify(mock, atMost(3)).method();

Example:

import org.junit.jupiter.api.Test;

import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;

class NotificationServiceTest {

    @Test
    void sendWelcomeEmail_shouldNotifyUser() {
        EmailSender emailSender = mock(EmailSender.class);
        NotificationService notificationService = new NotificationService(emailSender);

        notificationService.sendWelcomeEmail("[email protected]");

        verify(emailSender, atLeastOnce()).send("[email protected]", "Welcome!");
    }
}

3. Verifying Arguments with Matchers

If you do not want to match the exact object, you can use argument matchers.

import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.verify;

verify(userRepository).save(any(User.class));

For specific values:

verify(emailSender).send(eq("[email protected]"), eq("Welcome!"));

You can also mix broad and specific matching:

verify(emailSender).send(eq("[email protected]"), any(String.class));

Important: if you use matchers for one argument, use matchers for all arguments in that method call.

Correct:

verify(emailSender).send(eq("[email protected]"), any(String.class));

Avoid mixing raw values and matchers:

verify(emailSender).send("[email protected]", any(String.class));

4. Verifying No Calls

To verify that a mock had no interactions:

import static org.mockito.Mockito.verifyNoInteractions;

verifyNoInteractions(userRepository);

To verify that no more calls happened after the expected ones:

import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;

verify(userRepository).save(user);
verifyNoMoreInteractions(userRepository);

Example:

@Test
void register_invalidUser_shouldNotSaveUser() {
    UserRepository userRepository = mock(UserRepository.class);
    UserService userService = new UserService(userRepository);

    User invalidUser = new User();

    userService.register(invalidUser);

    verifyNoInteractions(userRepository);
}

5. Verifying Order of Calls

Use InOrder when the order matters.

import org.junit.jupiter.api.Test;
import org.mockito.InOrder;

import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.mock;

class OrderServiceTest {

    @Test
    void processOrder_shouldCallMethodsInOrder() {
        PaymentService paymentService = mock(PaymentService.class);
        InventoryService inventoryService = mock(InventoryService.class);

        OrderService orderService = new OrderService(paymentService, inventoryService);

        Order order = new Order();

        orderService.process(order);

        InOrder inOrder = inOrder(inventoryService, paymentService);

        inOrder.verify(inventoryService).reserve(order);
        inOrder.verify(paymentService).charge(order);
    }
}

6. Using @Mock with JUnit 5

Instead of manually creating mocks with mock(), you can use Mockito’s JUnit 5 extension.

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;

import static org.mockito.Mockito.verify;

@ExtendWith(MockitoExtension.class)
class UserServiceTest {

    @Mock
    private UserRepository userRepository;

    @InjectMocks
    private UserService userService;

    @Test
    void register_shouldSaveUser() {
        User user = new User();

        userService.register(user);

        verify(userRepository).save(user);
    }
}

Here:

@Mock
private UserRepository userRepository;

creates a mock repository.

@InjectMocks
private UserService userService;

creates the service and injects the mock into it.

7. Capturing Arguments with ArgumentCaptor

Use ArgumentCaptor when you want to inspect the object passed to a method.

import org.junit.jupiter.api.Test;
import org.mockito.ArgumentCaptor;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;

class UserServiceTest {

    @Test
    void register_shouldSaveUserWithExpectedName() {
        UserRepository userRepository = mock(UserRepository.class);
        UserService userService = new UserService(userRepository);

        userService.register("Alice");

        ArgumentCaptor<User> userCaptor = ArgumentCaptor.forClass(User.class);

        verify(userRepository).save(userCaptor.capture());

        User savedUser = userCaptor.getValue();

        assertEquals("Alice", savedUser.getName());
    }
}

This is useful when the method under test creates a new object internally, so you cannot verify using the exact same instance.

8. Verifying Exceptions Still Triggers Calls

You can combine JUnit assertions with Mockito verification.

import org.junit.jupiter.api.Test;

import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;

class PaymentServiceTest {

    @Test
    void pay_invalidAmount_shouldLogFailure() {
        AuditLogger auditLogger = mock(AuditLogger.class);
        PaymentService paymentService = new PaymentService(auditLogger);

        assertThrows(IllegalArgumentException.class, () -> {
            paymentService.pay(-10);
        });

        verify(auditLogger).log("Invalid payment amount: -10");
    }
}

9. Maven Dependencies

For JUnit 5 and Mockito, add dependencies like these:

<dependencies>
    <dependency>
        <groupId>org.junit.jupiter</groupId>
        <artifactId>junit-jupiter</artifactId>
        <version>5.13.4</version>
        <scope>test</scope>
    </dependency>

    <dependency>
        <groupId>org.mockito</groupId>
        <artifactId>mockito-core</artifactId>
        <version>5.18.0</version>
        <scope>test</scope>
    </dependency>

    <dependency>
        <groupId>org.mockito</groupId>
        <artifactId>mockito-junit-jupiter</artifactId>
        <version>5.18.0</version>
        <scope>test</scope>
    </dependency>
</dependencies>

10. Gradle Dependencies

dependencies {
    testImplementation 'org.junit.jupiter:junit-jupiter:5.13.4'
    testImplementation 'org.mockito:mockito-core:5.18.0'
    testImplementation 'org.mockito:mockito-junit-jupiter:5.18.0'

    testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
}

test {
    useJUnitPlatform()
}

Common Verification Patterns

verify(repository).save(user);
verify(repository, times(1)).save(user);
verify(repository, never()).delete(user);
verify(repository, atLeastOnce()).save(any(User.class));
verify(repository, atMost(3)).findByEmail(any(String.class));
verifyNoInteractions(repository);
verifyNoMoreInteractions(repository);

Summary

Use Mockito’s verify() when you want to test interactions between objects.

Typical usage:

verify(mock).method(argument);

For example:

verify(userRepository).save(user);

Use verification when the important result of a method is not just a returned value, but that another dependency was called correctly.

How do I use @Mock and @InjectMocks with JUnit?

To use @Mock and @InjectMocks with JUnit, you typically use them with Mockito.

  • @Mock creates a fake/mock dependency.
  • @InjectMocks creates the class under test and injects the mocks into it.
  • With JUnit 5, you enable Mockito using @ExtendWith(MockitoExtension.class).

1. Add Mockito dependencies

Maven

<dependencies>
    <dependency>
        <groupId>org.junit.jupiter</groupId>
        <artifactId>junit-jupiter</artifactId>
        <version>5.11.4</version>
        <scope>test</scope>
    </dependency>

    <dependency>
        <groupId>org.mockito</groupId>
        <artifactId>mockito-junit-jupiter</artifactId>
        <version>5.14.2</version>
        <scope>test</scope>
    </dependency>
</dependencies>

Gradle

dependencies {
    testImplementation 'org.junit.jupiter:junit-jupiter:5.11.4'
    testImplementation 'org.mockito:mockito-junit-jupiter:5.14.2'
}

test {
    useJUnitPlatform()
}

2. Example class to test

Suppose you have a service that depends on a repository:

public class UserService {
    private final UserRepository userRepository;

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

    public String getUsernameById(Long id) {
        User user = userRepository.findById(id);

        if (user == null) {
            return "Unknown";
        }

        return user.getName();
    }
}

Repository:

public interface UserRepository {
    User findById(Long id);
}

Model:

public class User {
    private final Long id;
    private final String name;

    public User(Long id, String name) {
        this.id = id;
        this.name = name;
    }

    public Long getId() {
        return id;
    }

    public String getName() {
        return name;
    }
}

3. Use @Mock and @InjectMocks

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.Mockito.when;

@ExtendWith(MockitoExtension.class)
class UserServiceTest {

    @Mock
    private UserRepository userRepository;

    @InjectMocks
    private UserService userService;

    @Test
    void getUsernameByIdReturnsUserNameWhenUserExists() {
        when(userRepository.findById(1L))
                .thenReturn(new User(1L, "Alice"));

        String result = userService.getUsernameById(1L);

        assertEquals("Alice", result);
    }

    @Test
    void getUsernameByIdReturnsUnknownWhenUserDoesNotExist() {
        when(userRepository.findById(99L))
                .thenReturn(null);

        String result = userService.getUsernameById(99L);

        assertEquals("Unknown", result);
    }
}

How it works

@Mock
private UserRepository userRepository;

This tells Mockito to create a mock implementation of UserRepository.

@InjectMocks
private UserService userService;

This tells Mockito to create a UserService instance and inject the mocked UserRepository into it.

Mockito tries injection in this order:

  1. Constructor injection
  2. Setter injection
  3. Field injection

Constructor injection is usually the best option because it makes dependencies explicit and easier to test.

Verifying mock interactions

You can also verify that a dependency method was called:

import static org.mockito.Mockito.verify;

@Test
void getUsernameByIdCallsRepository() {
    when(userRepository.findById(1L))
            .thenReturn(new User(1L, "Alice"));

    userService.getUsernameById(1L);

    verify(userRepository).findById(1L);
}

Common mistake: forgetting Mockito extension

If you forget this:

@ExtendWith(MockitoExtension.class)

then your @Mock fields may remain null, causing a NullPointerException.

JUnit 4 version

If you are using JUnit 4, use @RunWith(MockitoJUnitRunner.class) instead:

import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;

import static org.junit.Assert.assertEquals;
import static org.mockito.Mockito.when;

@RunWith(MockitoJUnitRunner.class)
public class UserServiceTest {

    @Mock
    private UserRepository userRepository;

    @InjectMocks
    private UserService userService;

    @Test
    public void getUsernameByIdReturnsUserNameWhenUserExists() {
        when(userRepository.findById(1L))
                .thenReturn(new User(1L, "Alice"));

        String result = userService.getUsernameById(1L);

        assertEquals("Alice", result);
    }
}

Quick summary

Use this pattern for JUnit 5:

@ExtendWith(MockitoExtension.class)
class MyServiceTest {

    @Mock
    private MyRepository repository;

    @InjectMocks
    private MyService service;

    @Test
    void testSomething() {
        when(repository.findSomething()).thenReturn("value");

        String result = service.doSomething();

        assertEquals("value", result);
    }
}

Use @Mock for dependencies and @InjectMocks for the class you are testing.