Здійснивши глузливий метод, поверніть аргумент, який йому передали


673

Розглянемо підпис методу, як:

public String myFunction(String abc);

Чи може Mockito допомогти повернути той самий рядок, що отриманий методом?


Гаразд, як щодо будь-якого знущального фреймворку Java взагалі ... Чи можливо це з будь-яким іншим фреймворком, або я повинен просто створити тупу заглушку, щоб імітувати поведінку, яку я хочу?
Абхієет Кашня

Відповіді:


1001

Ви можете створити відповідь у Mockito. Припустимо, у нас є інтерфейс з назвою Application з методом myFunction.

public interface Application {
  public String myFunction(String abc);
}

Ось метод тестування з відповіддю Mockito:

public void testMyFunction() throws Exception {
  Application mock = mock(Application.class);
  when(mock.myFunction(anyString())).thenAnswer(new Answer<String>() {
    @Override
    public String answer(InvocationOnMock invocation) throws Throwable {
      Object[] args = invocation.getArguments();
      return (String) args[0];
    }
  });

  assertEquals("someString",mock.myFunction("someString"));
  assertEquals("anotherString",mock.myFunction("anotherString"));
}

Оскільки Mockito 1.9.5 та Java 8, ви також можете використовувати лямбда-вираз:

when(myMock.myFunction(anyString())).thenAnswer(i -> i.getArguments()[0]);

1
Це я теж шукав. Дякую! Однак моя проблема була іншою. Я хочу знущатися над службою збереження (EJB), яка зберігає об’єкти та повертає їх по імені.
migu

7
Я створив додатковий клас, який завершує створення відповіді. Тож код звучить такwhen(...).then(Return.firstParameter())
SpaceTrucker

69
З лямбдами Java 8 повертається перший аргумент легко навіть для конкретного класу, тобто when(foo(any()).then(i -> i.getArgumentAt(0, Bar.class)). І ви можете так само добре використовувати посилання на метод і викликати реальний метод.
Paweł Dyda

Це вирішує мою проблему з методом віддачі, Iterator<? extends ClassName>який викликає всілякі проблеми з кидком у thenReturn()виписці.
Майкл Шопсін

16
З Java 8 та Mockito <1.9.5 тоді відповідь Павела стаєwhen(foo(any()).thenAnswer(i -> i.getArguments()[0])
Graeme Moss

566

Якщо у вас Mockito 1.9.5 або новішої версії, є новий статичний метод, який може зробити Answerоб'єкт для вас. Вам потрібно написати щось на кшталт

import static org.mockito.Mockito.when;
import static org.mockito.AdditionalAnswers.returnsFirstArg;

when(myMock.myFunction(anyString())).then(returnsFirstArg());

або альтернативно

doAnswer(returnsFirstArg()).when(myMock).myFunction(anyString());

Зауважте, що returnsFirstArg()метод є статичним у AdditionalAnswersкласі, що є новим для Mockito 1.9.5; тому вам потрібен правильний статичний імпорт.


17
Примітка: це when(...).then(returnsFirstArg())я помилково мав, when(...).thenReturn(returnsFirstArg())що давjava.lang.ClassCastException: org.mockito.internal.stubbing.answers.ReturnsArgumentAt cannot be cast to
Бенедикт Кьопель

1
Примітка: returnFirstArg () повертає Answer <>, а не значення аргументу. Отримав "Foo (java.lang.String) не можна застосувати до '(org.mockito.stubbing.Answer <java.lang.Object>)' під час спроби зателефонувати .thenReturn (новий Foo (returnFirstArg ()))
Lu55

Мені завжди потрібно повторювати цю відповідь знову і знову і знову протягом останніх років, оскільки я просто не можу згадати "AdditionalAnswers", і мені це дуже рідко потрібно. Тоді мені цікаво, як чорт я можу створити цей сценарій, оскільки не можу знайти необхідні залежності. Чи не можна це просто додати безпосередньо до макету? : /
BAERUS

2
Відповідь Стіва більш загальна. Цей лише дозволяє повернути необроблений аргумент. Якщо ви хочете обробити цей аргумент і повернути результат, то відповідь Стіва правила. Я підтримав обидва, оскільки вони обидва корисні.
акостадінов

FYI, ми повинні імпортувати static org.mockito.AdditionalAnswers.returnsFirstArg. це використовувати returnFirstArg. Також я можу зробити when(myMock.myFunction(any())).then(returnsFirstArg())в Mockito 2.20. *
gtiwari333

77

За допомогою Java 8 можна створити однорядну відповідь навіть у старшій версії Mockito:

when(myMock.myFunction(anyString()).then(i -> i.getArgumentAt(0, String.class));

Звичайно, це не так корисно, як використання AdditionalAnswersзапропонованого Девідом Уоллес, але може бути корисним, якщо ви хочете перетворити аргумент "на льоту".


1
Блискуча. Дякую. Якщо аргумент є long, чи може це все-таки працювати з боксом і Long.class?
vikingsteve

1
.getArgumentAt (..) не знайдено для мене, але .getArgument (1) працював (mockito 2.6.2)
Кертіс Яллоп

41

У мене була дуже схожа проблема. Метою було знущатися над службою, яка зберігає Об'єкти і може повернути їх за своїм ім'ям. Послуга виглядає так:

public class RoomService {
    public Room findByName(String roomName) {...}
    public void persist(Room room) {...}
}

Сервісний макет використовує карту для зберігання екземплярів кімнати.

RoomService roomService = mock(RoomService.class);
final Map<String, Room> roomMap = new HashMap<String, Room>();

// mock for method persist
doAnswer(new Answer<Void>() {
    @Override
    public Void answer(InvocationOnMock invocation) throws Throwable {
        Object[] arguments = invocation.getArguments();
        if (arguments != null && arguments.length > 0 && arguments[0] != null) {
            Room room = (Room) arguments[0];
            roomMap.put(room.getName(), room);
        }
        return null;
    }
}).when(roomService).persist(any(Room.class));

// mock for method findByName
when(roomService.findByName(anyString())).thenAnswer(new Answer<Room>() {
    @Override
    public Room answer(InvocationOnMock invocation) throws Throwable {
        Object[] arguments = invocation.getArguments();
        if (arguments != null && arguments.length > 0 && arguments[0] != null) {
            String key = (String) arguments[0];
            if (roomMap.containsKey(key)) {
                return roomMap.get(key);
            }
        }
        return null;
    }
});

Тепер ми можемо запустити свої тести на цьому макеті. Наприклад:

String name = "room";
Room room = new Room(name);
roomService.persist(room);
assertThat(roomService.findByName(name), equalTo(room));
assertNull(roomService.findByName("none"));

34

З Java 8 відповідь Стіва може стати

public void testMyFunction() throws Exception {
    Application mock = mock(Application.class);
    when(mock.myFunction(anyString())).thenAnswer(
    invocation -> {
        Object[] args = invocation.getArguments();
        return args[0];
    });

    assertEquals("someString", mock.myFunction("someString"));
    assertEquals("anotherString", mock.myFunction("anotherString"));
}

EDIT: Ще коротше:

public void testMyFunction() throws Exception {
    Application mock = mock(Application.class);
    when(mock.myFunction(anyString())).thenAnswer(
        invocation -> invocation.getArgument(0));

    assertEquals("someString", mock.myFunction("someString"));
    assertEquals("anotherString", mock.myFunction("anotherString"));
}

6

Це досить старе питання, але я думаю, що все ще актуальне. Також прийнята відповідь працює лише для String. Тим часом є Mockito 2.1, і деякий імпорт змінився, тому я хотів би поділитися моєю поточною відповіддю:

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

@Mock
private MyClass myClass;

// this will return anything you pass, but it's pretty unrealistic
when(myClass.myFunction(any())).then(returnsFirstArg());
// it is more "life-like" to accept only the right type
when(myClass.myFunction(any(ClassOfArgument.class))).then(returnsFirstArg());

MyClass.myFunction виглядатиме так:

public class MyClass {
    public ClassOfArgument myFunction(ClassOfArgument argument){
        return argument;
    }  
}

4

Я використовую щось подібне (в основному це той самий підхід). Іноді корисно, щоб макет об'єкта повертав попередньо визначений вихід для певних входів. Це іде так:

private Hashtable<InputObject,  OutputObject> table = new Hashtable<InputObject, OutputObject>();
table.put(input1, ouput1);
table.put(input2, ouput2);

...

when(mockObject.method(any(InputObject.class))).thenAnswer(
       new Answer<OutputObject>()
       {
           @Override
           public OutputObject answer(final InvocationOnMock invocation) throws Throwable
           {
               InputObject input = (InputObject) invocation.getArguments()[0];
               if (table.containsKey(input))
               {
                   return table.get(input);
               }
               else
               {
                   return null; // alternatively, you could throw an exception
               }
           }
       }
       );

4

Ви можете використовувати verify () у поєднанні з ArgumentCaptor для забезпечення виконання тесту та ArgumentCaptor для оцінки аргументів:

ArgumentCaptor<String> argument = ArgumentCaptor.forClass(String.class);
verify(mock).myFunction(argument.capture());
assertEquals("the expected value here", argument.getValue());

Значення аргументу очевидно доступне через argument.getValue () для подальшого маніпулювання / перевірки / будь-якого іншого.


3

Це трохи по-старому, але я прийшов сюди, бо в мене був той самий випуск. Я використовую JUnit, але цього разу в додатку Котліна з макетом. Я розміщую зразок тут для ознайомлення та порівняння з колегою Java:

@Test
fun demo() {
  // mock a sample function
  val aMock: (String) -> (String) = mockk()

  // make it return the same as the argument on every invocation
  every {
    aMock.invoke(any())
  } answers {
    firstArg()
  }

  // test it
  assertEquals("senko", aMock.invoke("senko"))
  assertEquals("senko1", aMock.invoke("senko1"))
  assertNotEquals("not a senko", aMock.invoke("senko"))
}

2

Ви можете досягти цього, використовуючи ArgumentCaptor

Уявіть, у вас є така функція квасолі.

public interface Application {
  public String myFunction(String abc);
}

Потім у вашому тестовому класі:

//Use ArgumentCaptor to capture the value
ArgumentCaptor<String> param = ArgumentCaptor.forClass(String.class);


when(mock.myFunction(param.capture())).thenAnswer(new Answer<String>() {
    @Override
    public String answer(InvocationOnMock invocation) throws Throwable {
      return param.getValue();//return the captured value.
    }
  });

АБО якщо ви любите лямбда, просто зробіть:

//Use ArgumentCaptor to capture the value
ArgumentCaptor<String> param = ArgumentCaptor.forClass(String.class);


when(mock.myFunction(param.capture()))
    .thenAnswer((invocation) -> param.getValue());

Резюме: Використовуйте аргумент captor, щоб захопити переданий параметр. Пізніше у відповідь поверніть значення, захоплене за допомогою getValue.


Це більше не працює (більше?). Щодо документів: Цей метод потрібно використовувати всередині верифікації. Це означає, що ви можете зафіксувати значення лише за допомогою методу підтвердження
Мухаммед Місір

1. Не впевнений, що ти маєш на увазі, коли This doesn´t work (anymore?).я працюю над моєю інстанцією. 2. Вибачте, мені не зрозуміло питання, яке ви намагаєтеся зробити. Відповідь конкретна на питання ОП.
Кирило Черіан
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.