Как заставить мок-метод возвращать переданный ему аргумент в Mockito?
Решение: использование Answer для возврата модифицированного аргумента
Проблема в том, что thenReturn() возвращает фиксированный объект, который вы передаёте. Чтобы вернуть переданный аргумент с изменениями, нужно использовать Answer — интерфейс, который позволяет выполнять произвольный код при вызове метода мока.
В вашем случае нужно создать реализацию Answer, которая:
- Извлекает аргумент, переданный в метод (первый и единственный аргумент типа
User). - Устанавливает ему ID (например, через сеттер или рефлексию).
- Возвращает модифицированный объект.
Пример кода с использованием Answer
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
@Test
public void testCreateUser() {
User userInput = new User("John", "john@example.com");
when(userRepository.save(any(User.class))).thenAnswer(new Answer<User>() {
@Override
public User answer(InvocationOnMock invocation) throws Throwable {
// Получаем первый аргумент (переданный User)
User arg = invocation.getArgument(0);
// Устанавливаем ID (например, 123)
arg.setId(123L);
return arg;
}
});
User result = userService.createUser("John", "john@example.com");
assertEquals("John", result.getName());
assertEquals(Long.valueOf(123), result.getId()); // Теперь работает!
}
Более краткая версия с лямбдой (Java 8+)
@Test
public void testCreateUser() {
when(userRepository.save(any(User.class))).thenAnswer(invocation -> {
User arg = invocation.getArgument(0);
arg.setId(123L);
return arg;
});
User result = userService.createUser("John", "john@example.com");
assertEquals("John", result.getName());
assertEquals(Long.valueOf(123), result.getId());
}
Важные моменты
- Изменение оригинального объекта: В этом примере мы модифицируем объект, который был передан в мок. Это может повлиять на другие части теста, если вы используете тот же объект позже. Если нужно избежать мутации, создавайте копию.
- Генерация ID: В реальном приложении ID генерируется базой данных. В тесте вы можете использовать счётчик или случайное число для имитации.
- Использование с несколькими аргументами: Если метод принимает несколько аргументов, используйте
invocation.getArgument(index)с нужным индексом.
Альтернатива: использование thenAnswer с method.getArgumentAt()
Если вы используете старую версию Mockito или хотите явно указать тип, можно использовать invocation.getArgumentAt(0, User.class) (устарело в новых версиях, используйте getArgument(0)).
when(userRepository.save(any(User.class))).thenAnswer(invocation -> {
User arg = invocation.getArgumentAt(0, User.class); // для старых версий
arg.setId(123L);
return arg;
});
Полный рабочий пример
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.ArgumentMatchers.any;
import static org.mockito.Mockito.when;
import static org.junit.jupiter.api.Assertions.*;
@ExtendWith(MockitoExtension.class)
public class UserServiceTest {
@Mock
private UserRepository userRepository;
@InjectMocks
private UserService userService;
@Test
public void testCreateUser() {
when(userRepository.save(any(User.class))).thenAnswer(invocation -> {
User user = invocation.getArgument(0);
user.setId(123L);
return user;
});
User result = userService.createUser("John", "john@example.com");
assertEquals("John", result.getName());
assertEquals(Long.valueOf(123), result.getId());
}
}
Теперь ваш тест будет работать, и мок будет возвращать объект с установленным ID, как если бы это сделала база данных.
<<Дополнительный подход: использование Mockito.spy для частичного мокирования
Если ваш UserRepository — это реальный класс (не интерфейс), и вы хотите сохранить часть его поведения, можно использовать spy вместо mock. Это позволит вызвать реальный метод save(), но с возможностью переопределить его части.
Однако для вашей задачи это менее удобно, чем Answer, потому что:
- Spy требует реального экземпляра репозитория (например, с подключением к тестовой БД).
- Вы всё равно не сможете легко сгенерировать ID, не вызывая реальную БД.
Лучше использовать Answer, как описано в первом ответе. Но если вам нужно просто вернуть переданный аргумент без изменений (например, для методов, которые не меняют объект), можно использовать встроенный AdditionalAnswers.returnsFirstArg() (доступен в Mockito 2.x+):
import static org.mockito.AdditionalAnswers.returnsFirstArg;
when(userRepository.save(any(User.class))).then(returnsFirstArg());
Этот метод возвращает первый аргумент как есть, без модификаций. Если нужно модифицировать, используйте Answer с лямбдой.
Ответов пока нет.
Войдите чтобы оставить ответ
Блоги
Работа с файловой системой в C++: полное руководство
Как написать REST API на C++ за 30 минут: пошаговое руководство