Mockito doAnswer()

Introduction

The doAnswer() method in Mockito is used to specify custom behavior for a method call on a mock object. This is particularly useful when you need to perform more complex operations or handle specific cases that cannot be easily managed with doReturn(), doThrow(), or when(). It allows you to define what should happen when a method on the mock object is called.

When to Use doAnswer()

Use doAnswer() when you need to:

  1. Perform complex logic: When the behavior of a method cannot be easily defined by simple return values or exceptions.
  2. Capture method arguments: When you need to inspect or modify the arguments passed to the mock method.
  3. Invoke a callback or listener: When your method needs to trigger some action in response to being called.
  4. Combine multiple behaviors: When you need to perform several actions in response to a single method call.

How doAnswer() Works

The doAnswer() method allows you to define a custom Answer implementation that specifies what should happen when the method is called. This Answer can access the arguments passed to the method, perform any necessary logic, and return a value or throw an exception.

Example Scenario

We will create a UserService class that has a dependency on a UserRepository. Our goal is to test the UserService methods using the doAnswer() method in Mockito to handle custom behavior.

UserService and UserRepository Classes

First, create the User class, the UserRepository interface, and the UserService class.

public class User {
    private String name;
    private String email;

    // Constructor, getters, and setters
    public User(String name, String email) {
        this.name = name;
        this.email = email;
    }

    public String getName() {
        return name;
    }

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

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }
}

public interface UserRepository {
    void saveUser(User user);
    User findUserByEmail(String email);
}

public class UserService {
    private final UserRepository userRepository;

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

    public void registerUser(String name, String email) {
        User user = new User(name, email);
        userRepository.saveUser(user);
    }

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

JUnit 5 Test Class with Mockito

Create a test class for UserService using JUnit 5 and Mockito.

import static org.mockito.Mockito.*;
import static org.junit.jupiter.api.Assertions.*;
import org.junit.jupiter.api.Test;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
import org.mockito.junit.jupiter.MockitoExtension;
import org.junit.jupiter.api.extension.ExtendWith;

@ExtendWith(MockitoExtension.class)
public class UserServiceTest {

    @Mock
    private UserRepository userRepository;

    @InjectMocks
    private UserService userService;

    @Test
    public void testRegisterUser() {
        // Given
        String name = "Ramesh Fadatare";
        String email = "[email protected]";

        doAnswer(new Answer<Void>() {
            @Override
            public Void answer(InvocationOnMock invocation) throws Throwable {
                User user = invocation.getArgument(0);
                assertEquals("Ramesh Fadatare", user.getName());
                assertEquals("[email protected]", user.getEmail());
                return null;
            }
        }).when(userRepository).saveUser(any(User.class));

        // When
        userService.registerUser(name, email);

        // Then
        verify(userRepository).saveUser(any(User.class));
    }

    @Test
    public void testGetUserByEmail() {
        // Given
        String email = "[email protected]";
        User user = new User("Anita Patil", email);
        doAnswer(new Answer<User>() {
            @Override
            public User answer(InvocationOnMock invocation) throws Throwable {
                String email = invocation.getArgument(0);
                if ("[email protected]".equals(email)) {
                    return user;
                } else {
                    return null;
                }
            }
        }).when(userRepository).findUserByEmail(anyString());

        // When
        User result = userService.getUserByEmail(email);

        // Then
        assertNotNull(result);
        assertEquals("Anita Patil", result.getName());
        assertEquals(email, result.getEmail());
    }
}

Explanation

  1. Creating Mocks with @Mock:

    • The @Mock annotation creates a mock instance of the UserRepository interface. This mock instance can be used to simulate the behavior of the UserRepository in a controlled way.
  2. Injecting Mocks with @InjectMocks:

    • The @InjectMocks annotation injects the mock UserRepository into the UserService instance to provide a controlled test environment. This allows the UserService methods to be tested in isolation from the actual UserRepository implementation.
  3. Defining Custom Behavior with doAnswer():

    • The doAnswer(new Answer<Void>() { ... }).when(userRepository).saveUser(any(User.class)); method configures the mock UserRepository to execute custom behavior when the saveUser method is called with any User object.
    • Inside the answer method, we retrieve the arguments passed to the saveUser method and perform assertions to verify the user's name and email. This allows us to check that the registerUser method of the UserService class correctly interacts with the UserRepository.
    • Similarly, the doAnswer(new Answer<User>() { ... }).when(userRepository).findUserByEmail(anyString()); method configures the mock UserRepository to return a specific User object when the findUserByEmail method is called with the specified email. This allows us to check that the getUserByEmail method of the UserService class correctly interacts with the UserRepository.
  4. Verifying Interactions with verify():

    • The verify(userRepository).saveUser(any(User.class)); method checks if the saveUser method was called on the UserRepository with any User object. This ensures that the registerUser method of the UserService class interacts with the UserRepository correctly.

Use Cases

Use Case 1: Performing Complex Logic

@Test
public void testComplexLogicInMethodCall() {
    // Given
    String email = "[email protected]";
    User user = new User("Ravi Kumar", email);

    doAnswer(new Answer<User>() {
        @Override
        public User answer(InvocationOnMock invocation) throws Throwable {
            String email = invocation.getArgument(0);
            // Perform complex logic
            if ("[email protected]".equals(email)) {
                return user;
            } else {
                return null;
            }
        }
    }).when(userRepository).findUserByEmail(anyString());

    // When
    User result = userService.getUserByEmail(email);

    // Then
    assertNotNull(result);
    assertEquals("Ravi Kumar", result.getName());
    assertEquals(email, result.getEmail());
}

Use Case 2: Capturing and Modifying Arguments

@Test
public void testCaptureAndModifyArguments() {
    // Given
    String name = "Anjali Sharma";
    String email = "[email protected]";

    doAnswer(new Answer<Void>() {
        @Override
        public Void answer(InvocationOnMock invocation) throws Throwable {
            User user = invocation.getArgument(0);
            user.setName(user.getName().toUpperCase());
            return null;
        }
    }).when(userRepository).saveUser(any(User.class));

    // When
    userService.registerUser(name, email);

    // Then
    verify(userRepository).saveUser(argThat(user -> "ANJALI SHARMA".equals(user.getName()) && email.equals(user.getEmail())));
}

Use Case 3: Invoking a Callback

@Test
public void testInvokeCallback() {
    // Given
    String name = "Rajesh Verma";
    String email = "[email protected]";

    doAnswer(new Answer<Void>() {
        @Override
        public Void answer(InvocationOnMock invocation) throws Throwable {
            // Invoke a callback
            Runnable callback = invocation.getArgument(1);
            callback.run();
            return null;
        }
    }).when(userRepository).saveUser(any(User.class), any(Runnable.class));

    // When
    userService.registerUser(name, email);

    // Then
    verify(userRepository).saveUser(any(User.class), any(Runnable.class));
}

Conclusion

The doAnswer() method in Mockito simplifies the configuration of custom behavior for method calls on mock objects for unit testing. By using doAnswer(), you can handle complex scenarios and perform specific actions when methods on mock objects are called. 

Related Mockito Methods

Mockito mock()
Mockito spy()
Mockito when()
Mockito thenThrow()
Mockito verify()
Mockito times()
Mockito never()
Mockito any()
Mockito eq()
Mockito inOrder()
Mockito doReturn()
Mockito doThrow()
Mockito doAnswer()
Mockito timeout()
Mockito ArgumentMatchers

Comments