Java > Testing in Java > Unit Testing > Mocking with Mockito

Unit Testing with Mockito: Basic Example

This snippet demonstrates how to use Mockito to create a mock object and verify its interactions during unit testing in Java. We will explore creating a simple service and a repository, mocking the repository, and verifying that the service calls the repository as expected. This isolates the service's logic from the real data layer during testing.

Defining the Repository Interface

This interface represents a data access layer. It defines the methods for retrieving and saving user data. In a real application, this would interact with a database.

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

Defining the User Service

This class represents a service that utilizes the UserRepository. The constructor takes a UserRepository instance, allowing for dependency injection and making it easier to mock during testing. The `getUser` method retrieves a user by ID, and the `createUser` method saves a new user after performing some validation.

public class UserService {

    private final UserRepository userRepository;

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

    public User getUser(Long id) {
        return userRepository.findById(id);
    }

    public void createUser(User user) {
        // Perform some business logic before saving (e.g., validation)
        if (user.getName() == null || user.getName().isEmpty()) {
            throw new IllegalArgumentException("User name cannot be empty.");
        }
        userRepository.save(user);
    }
}

Creating the User Class

Simple `User` POJO class with id and name.

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

    public User() {}

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

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

Unit Test with Mockito

This test class demonstrates how to use Mockito to test the UserService. * We first create a mock UserRepository using `Mockito.mock(UserRepository.class)`. This creates a mock object that implements the UserRepository interface. * Then, we create an instance of UserService, injecting the mocked UserRepository. * We use `when(userRepository.findById(1L)).thenReturn(expectedUser)` to define the behavior of the mock object. This tells Mockito that when the `findById` method is called with the argument `1L`, it should return the `expectedUser`. * We then call the `getUser` method on the UserService and assert that the returned user is equal to the `expectedUser`. * Finally, we use `verify(userRepository, times(1)).findById(1L)` to verify that the `findById` method was called exactly once with the argument `1L`. The `createUser` tests check that the `save` method is called correctly and that an exception is thrown when an invalid user is provided. `never()` is used to verify that the method isn't called when an exception is expected.

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

import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.Mockito.*;

class UserServiceTest {

    @Test
    void getUser_validId_returnsUser() {
        // Arrange
        UserRepository userRepository = Mockito.mock(UserRepository.class);
        UserService userService = new UserService(userRepository);
        User expectedUser = new User(1L, "John Doe");

        when(userRepository.findById(1L)).thenReturn(expectedUser);

        // Act
        User actualUser = userService.getUser(1L);

        // Assert
        assertEquals(expectedUser, actualUser);
        verify(userRepository, times(1)).findById(1L);
    }

    @Test
    void createUser_validUser_savesUser() {
        // Arrange
        UserRepository userRepository = Mockito.mock(UserRepository.class);
        UserService userService = new UserService(userRepository);
        User newUser = new User(null, "Jane Smith");

        // Act
        userService.createUser(newUser);

        // Assert
        verify(userRepository, times(1)).save(newUser);
    }

    @Test
    void createUser_invalidUser_throwsException() {
        // Arrange
        UserRepository userRepository = Mockito.mock(UserRepository.class);
        UserService userService = new UserService(userRepository);
        User invalidUser = new User(null, null);

        // Act & Assert
        assertThrows(IllegalArgumentException.class, () -> userService.createUser(invalidUser));
        verify(userRepository, never()).save(any());
    }
}

Concepts Behind the Snippet

This snippet illustrates Dependency Injection (DI) and Inversion of Control (IoC). The `UserService` doesn't create its own `UserRepository`; instead, it receives it as a constructor argument. This makes the `UserService` easier to test because we can inject a mock `UserRepository` that behaves in a predictable way. Mockito is used to create and configure these mock objects.

Real-Life Use Case

Consider a complex banking application where `UserService` handles user authentication and authorization. `UserRepository` could represent interaction with a database or external authentication service. By mocking the `UserRepository`, you can isolate the authentication logic within the `UserService` and test it independently from the actual data access layer. This speeds up testing and makes it more reliable, as you don't have to rely on a functioning database or external service for every test.

Best Practices

  • **Keep your tests focused:** Each test should only test one specific aspect of your code.
  • **Use descriptive names:** Name your tests clearly so that it's easy to understand what they're testing.
  • **Follow the Arrange-Act-Assert pattern:** Structure your tests to make them easy to read and understand.
  • **Don't mock what you don't own:** Avoid mocking classes that are part of the Java standard library or other third-party libraries. Focus on mocking your own classes and interfaces.
  • **Avoid over-mocking:** Only mock dependencies that are necessary to isolate the unit under test.

Interview Tip

Be prepared to explain the benefits of unit testing and mocking. Understand the Arrange-Act-Assert pattern and be able to discuss different types of mocking frameworks, such as Mockito and EasyMock. Also, be able to explain the concept of test-driven development (TDD).

When to Use Mockito

Mockito is beneficial when you need to isolate a unit of code (e.g., a class) from its dependencies for testing. It's particularly useful when dealing with:

  • **External dependencies:** Databases, web services, or file systems.
  • **Slow or unreliable dependencies:** Mocking allows you to create fast and reliable tests.
  • **Dependencies with complex setup:** Mocking can simplify the setup process for tests.
  • **Code that is difficult to test directly:** Mocking can make it easier to test specific scenarios.

Alternatives

  • **EasyMock:** Another popular Java mocking framework.
  • **PowerMock:** A framework that extends Mockito and EasyMock to allow mocking of static methods, final classes, and private methods (use with caution, as it can reduce code maintainability).
  • **Spring MockMvc:** For testing Spring MVC controllers without starting a full server.
  • **JUnit's built-in assertions:** For basic assertions without external libraries.

Pros

  • **Isolation:** Mocking allows you to isolate the unit under test, preventing dependencies from affecting the test results.
  • **Speed:** Mocking can speed up testing by eliminating the need to interact with slow or unreliable dependencies.
  • **Control:** Mocking allows you to control the behavior of dependencies, making it easier to test specific scenarios.
  • **Simplicity:** Mockito has a simple and intuitive API.

Cons

  • **Over-mocking:** Mocking too many dependencies can make tests brittle and difficult to maintain.
  • **Maintenance overhead:** Maintaining mocks can add to the overall maintenance overhead of the codebase.
  • **Potential for false positives:** If the mocks are not configured correctly, they can lead to false positives, where tests pass even though the code is broken.

FAQ

  • What's the difference between `when()` and `doReturn()` in Mockito?

    `when()` is the most common way to define stub behavior in Mockito. However, `doReturn()` is preferred when stubbing void methods or methods that throw exceptions. `when()` will actually execute the method being stubbed during the stubbing process, which can lead to unexpected behavior or errors, especially when the method has side effects. `doReturn()` avoids this by not executing the method during stubbing.
  • What is `verify()` used for in Mockito?

    `verify()` is used to ensure certain interactions with mock objects occurred during the test. You specify the mock object and the expected number of invocations for a specific method. This helps confirm that the unit under test interacts with its dependencies as intended.
  • Why is dependency injection important for unit testing with Mockito?

    Dependency injection makes it possible to replace real dependencies with mock objects. This allows you to isolate the class under test and control the behavior of its dependencies, which is essential for effective unit testing. Without DI, it's much harder (or impossible) to mock dependencies.