Back to Chapters

Chapter 10: Mocking and Integration

Introduction to Mocking

Mocking is a technique used in unit testing to isolate the code being tested by replacing dependencies with mock objects. This allows you to test your code in isolation, without being affected by external factors such as databases, web services, or other complex components.

Why Use Mocking?

There are several reasons to use mocking in your tests:

  1. Isolation: Test your code in isolation from its dependencies
  2. Speed: Tests run faster without real dependencies
  3. Reliability: Tests are more reliable when they don't depend on external systems
  4. Control: You can control the behavior of dependencies to test different scenarios
  5. Verification: You can verify that your code interacts correctly with its dependencies

Mockito Integration with TestNG

Mockito is one of the most popular mocking frameworks for Java. It integrates seamlessly with TestNG to provide powerful mocking capabilities.

Setting Up Mockito

To use Mockito with TestNG, you need to add the Mockito dependency to your project:

<dependency>
    <groupId>org.mockito</groupId>
    <artifactId>mockito-core</artifactId>
    <version>5.8.0</version> <!-- Use the latest version -->
    <scope>test</scope>
</dependency>

Basic Mocking with Mockito

Here's a simple example of using Mockito with TestNG:

import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.testng.Assert;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;

import static org.mockito.Mockito.*;

public class BasicMockingExample {

    // The class we want to test
    private UserService userService;

    // The dependency we want to mock
    @Mock
    private UserRepository userRepository;

    @BeforeMethod
    public void setup() {
        // Initialize mocks
        MockitoAnnotations.openMocks(this);

        // Create the service with the mock repository
        userService = new UserService(userRepository);
    }

    @Test
    public void testGetUserById() {
        // Arrange
        User expectedUser = new User(1, "John Doe", "john@example.com");
        when(userRepository.findById(1)).thenReturn(expectedUser);

        // Act
        User actualUser = userService.getUserById(1);

        // Assert
        Assert.assertEquals(actualUser, expectedUser, "User should match");

        // Verify that the repository method was called with the correct argument
        verify(userRepository).findById(1);
    }
}

// Classes used in the example
class User {
    private int id;
    private String name;
    private String email;

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

    // Getters and equals/hashCode methods omitted for brevity
}

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

class UserService {
    private final UserRepository userRepository;

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

    public User getUserById(int id) {
        return userRepository.findById(id);
    }

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

Stubbing Method Calls

You can stub method calls to make your mocks return specific values:

import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.testng.Assert;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;

import java.util.Arrays;
import java.util.List;

import static org.mockito.Mockito.*;

public class StubbingExample {

    @Mock
    private UserRepository userRepository;

    private UserService userService;

    @BeforeMethod
    public void setup() {
        MockitoAnnotations.openMocks(this);
        userService = new UserService(userRepository);
    }

    @Test
    public void testGetAllUsers() {
        // Arrange
        List<User> expectedUsers = Arrays.asList(
            new User(1, "John Doe", "john@example.com"),
            new User(2, "Jane Smith", "jane@example.com")
        );

        when(userRepository.findAll()).thenReturn(expectedUsers);

        // Act
        List<User> actualUsers = userService.getAllUsers();

        // Assert
        Assert.assertEquals(actualUsers, expectedUsers, "Users should match");
        verify(userRepository).findAll();
    }

    @Test
    public void testGetUserByEmail() {
        // Arrange
        User expectedUser = new User(1, "John Doe", "john@example.com");

        when(userRepository.findByEmail("john@example.com")).thenReturn(expectedUser);
        when(userRepository.findByEmail("nonexistent@example.com")).thenReturn(null);

        // Act
        User actualUser1 = userService.getUserByEmail("john@example.com");
        User actualUser2 = userService.getUserByEmail("nonexistent@example.com");

        // Assert
        Assert.assertEquals(actualUser1, expectedUser, "User should match");
        Assert.assertNull(actualUser2, "User should be null");

        verify(userRepository).findByEmail("john@example.com");
        verify(userRepository).findByEmail("nonexistent@example.com");
    }
}

// Additional methods for the classes
interface UserRepository {
    User findById(int id);
    User findByEmail(String email);
    List<User> findAll();
    void save(User user);
}

class UserService {
    private final UserRepository userRepository;

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

    public User getUserById(int id) {
        return userRepository.findById(id);
    }

    public User getUserByEmail(String email) {
        return userRepository.findByEmail(email);
    }

    public List<User> getAllUsers() {
        return userRepository.findAll();
    }

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

Verifying Interactions

You can verify that your code interacts correctly with its dependencies:

import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;

import static org.mockito.Mockito.*;

public class VerificationExample {

    @Mock
    private UserRepository userRepository;

    private UserService userService;

    @BeforeMethod
    public void setup() {
        MockitoAnnotations.openMocks(this);
        userService = new UserService(userRepository);
    }

    @Test
    public void testSaveUser() {
        // Arrange
        User user = new User(1, "John Doe", "john@example.com");

        // Act
        userService.saveUser(user);

        // Verify that the repository's save method was called with the correct user
        verify(userRepository).save(user);
    }

    @Test
    public void testDeleteUser() {
        // Arrange
        int userId = 1;

        // Act
        userService.deleteUser(userId);

        // Verify that the repository's delete method was called with the correct ID
        verify(userRepository).deleteById(userId);
    }

    @Test
    public void testUpdateUser() {
        // Arrange
        User user = new User(1, "John Doe", "john@example.com");

        // Act
        userService.updateUser(user);

        // Verify that the repository's update method was called with the correct user
        verify(userRepository).update(user);

        // Verify that no other methods were called on the repository
        verifyNoMoreInteractions(userRepository);
    }

    @Test
    public void testGetUserByIdNeverCalled() {
        // Verify that a method was never called
        verify(userRepository, never()).findById(anyInt());
    }
}

Advanced Mockito Features

Mockito provides advanced features for more complex testing scenarios.

Argument Matchers

You can use argument matchers to make your stubs and verifications more flexible:

import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.testng.Assert;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;

import static org.mockito.ArgumentMatchers.*;
import static org.mockito.Mockito.*;

public class ArgumentMatchersExample {

    @Mock
    private UserRepository userRepository;

    private UserService userService;

    @BeforeMethod
    public void setup() {
        MockitoAnnotations.openMocks(this);
        userService = new UserService(userRepository);
    }

    @Test
    public void testFindUserWithMatchers() {
        // Arrange
        User expectedUser = new User(1, "John Doe", "john@example.com");

        // Using argument matchers
        when(userRepository.findById(anyInt())).thenReturn(expectedUser);
        when(userRepository.findByEmail(contains("@example.com"))).thenReturn(expectedUser);

        // Act
        User user1 = userService.getUserById(1);
        User user2 = userService.getUserById(999);
        User user3 = userService.getUserByEmail("john@example.com");
        User user4 = userService.getUserByEmail("jane@example.com");

        // Assert
        Assert.assertEquals(user1, expectedUser, "User should match");
        Assert.assertEquals(user2, expectedUser, "User should match");
        Assert.assertEquals(user3, expectedUser, "User should match");
        Assert.assertEquals(user4, expectedUser, "User should match");
    }

    @Test
    public void testSaveUserWithMatchers() {
        // Act
        userService.saveUser(new User(1, "John Doe", "john@example.com"));

        // Verify with argument matchers
        verify(userRepository).save(argThat(user ->
            user.getName().equals("John Doe") &&
            user.getEmail().equals("john@example.com")
        ));
    }

    @Test
    public void testArgumentCaptor() {
        // Arrange
        ArgumentCaptor<User> userCaptor = ArgumentCaptor.forClass(User.class);
        User user = new User(1, "John Doe", "john@example.com");

        // Act
        userService.saveUser(user);

        // Verify and capture the argument
        verify(userRepository).save(userCaptor.capture());

        // Assert on the captured argument
        User capturedUser = userCaptor.getValue();
        Assert.assertEquals(capturedUser.getId(), 1, "User ID should match");
        Assert.assertEquals(capturedUser.getName(), "John Doe", "User name should match");
        Assert.assertEquals(capturedUser.getEmail(), "john@example.com", "User email should match");
    }
}

// Updated User class with getters
class User {
    private int id;
    private String name;
    private String email;

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

    public int getId() {
        return id;
    }

    public String getName() {
        return name;
    }

    public String getEmail() {
        return email;
    }

    // equals and hashCode methods omitted for brevity
}

Spying on Real Objects

Sometimes you want to use a real object but still track interactions with it:

import org.mockito.MockitoAnnotations;
import org.mockito.Spy;
import org.testng.Assert;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;

import java.util.ArrayList;
import java.util.List;

import static org.mockito.Mockito.*;

public class SpyExample {

    @Spy
    private List<String> spyList = new ArrayList<>();

    @BeforeMethod
    public void setup() {
        MockitoAnnotations.openMocks(this);
    }

    @Test
    public void testSpyList() {
        // Using the real method
        spyList.add("one");
        spyList.add("two");

        // Verify the real methods were called
        verify(spyList).add("one");
        verify(spyList).add("two");

        // The real list was modified
        Assert.assertEquals(spyList.size(), 2, "List should have 2 items");
        Assert.assertEquals(spyList.get(0), "one", "First item should be 'one'");

        // Stub a method
        when(spyList.size()).thenReturn(100);

        // Now the stubbed method returns the stubbed value
        Assert.assertEquals(spyList.size(), 100, "Stubbed size should be 100");

        // But other methods still work normally
        Assert.assertEquals(spyList.get(1), "two", "Second item should be 'two'");
    }

    @Test
    public void testSpyWithDoReturn() {
        // Add some items
        spyList.add("one");
        spyList.add("two");

        // This would throw an exception because get(5) is out of bounds
        // spyList.get(5);

        // But we can stub it to return a value
        doReturn("stubbed value").when(spyList).get(5);

        // Now it returns the stubbed value
        Assert.assertEquals(spyList.get(5), "stubbed value", "Should return stubbed value");
    }
}

Mocking Static Methods

With Mockito 3.4.0 and above, you can mock static methods:

import org.mockito.MockedStatic;
import org.testng.Assert;
import org.testng.annotations.Test;

import java.time.LocalDate;

import static org.mockito.Mockito.*;

public class StaticMethodMockingExample {

    @Test
    public void testMockingStaticMethod() {
        // Define a fixed date for testing
        LocalDate fixedDate = LocalDate.of(2025, 1, 1);

        // Mock the static method
        try (MockedStatic<LocalDate> mockedLocalDate = mockStatic(LocalDate.class)) {
            // Stub the static method
            mockedLocalDate.when(LocalDate::now).thenReturn(fixedDate);

            // Use a class that depends on LocalDate.now()
            DateProvider dateProvider = new DateProvider();
            LocalDate currentDate = dateProvider.getCurrentDate();

            // Verify the result
            Assert.assertEquals(currentDate, fixedDate, "Current date should match fixed date");

            // Verify the static method was called
            mockedLocalDate.verify(LocalDate::now);
        }
    }
}

class DateProvider {
    public LocalDate getCurrentDate() {
        return LocalDate.now();
    }

    public boolean isWeekend(LocalDate date) {
        return date.getDayOfWeek().getValue() >= 6;
    }
}