Направете подиграван метод да върне аргумент, който му е бил предаден

Помислете за подпис на метод като:

public String myFunction(String abc);

Може ли Mockito да помогне за връщането на същия низ, получен от метода?


person Abhijeet Kashnia    schedule 21.04.2010    source източник
comment
Добре, какво ще кажете за всяка рамка за подигравки на Java като цяло... Това възможно ли е с друга рамка, или трябва просто да създам тъп мъниче, за да имитирам поведението, което искам?   -  person Abhijeet Kashnia    schedule 22.04.2010


Отговори (10)


Можете да създадете отговор в Mockito. Да приемем, че имаме интерфейс с име Приложение с метод 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]);
person Steve    schedule 26.04.2010
comment
Това търсех и аз. Благодаря ти! Моят проблем обаче беше друг. Искам да се подиграя на услуга за постоянство (EJB), която съхранява обекти и ги връща по име. - person migu; 19.07.2011
comment
Създадох допълнителен клас, който обвива създаването на отговора. Така че кодът се чете като when(...).then(Return.firstParameter()) - person SpaceTrucker; 26.09.2012
comment
С Java 8 lambda е много лесно да върнете първия аргумент, дори за конкретен клас, т.е. when(foo(any()).then(i -> i.getArgumentAt(0, Bar.class)). И можете също така да използвате препратка към метод и да извикате истински метод. - person Paweł Dyda; 29.01.2015
comment
Това решава проблема ми с метод, който връща Iterator<? extends ClassName>, който причинява всякакви проблеми с кастинга в оператор thenReturn(). - person Michael Shopsin; 20.03.2015
comment
Как мога да разреша това, когато върнатият тип е void public void myFunction(String abc); - person SDS; 11.08.2015
comment
С Java 8 и Mockito ‹ 1.9.5 тогава отговорът на Paweł става when(foo(any()).thenAnswer(i -> i.getArguments()[0]) - person Graeme Moss; 31.08.2015
comment
@Steve, предлагам да актуализирате отговора си, за да включите промени от Mockito 1.9.5 и/или Java 8. Моля, вижте моята редакция например. Мисля, че това може да помогне на хората лесно да открият тази информация. - person borowis; 24.11.2016
comment
TLDR .thenAnswer { it.arguments[0] } - person Saba; 27.08.2020

Ако имате 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; така че ще ви е необходим правилният статичен импорт.

person Dawood ibn Kareem    schedule 07.08.2012
comment
Забележка: това е when(...).then(returnsFirstArg()), погрешка имах when(...).thenReturn(returnsFirstArg()), което даде java.lang.ClassCastException: org.mockito.internal.stubbing.answers.ReturnsArgumentAt cannot be cast to - person Benedikt Köppel; 11.03.2015
comment
Забележка: returnsFirstArg() връща Answer‹› вместо стойността на аргумента. Имам „Foo(java.lang.String) не може да се приложи към „(org.mockito.stubbing.Answer‹java.lang.Object›)“, докато се опитвате да извикате .thenReturn(new Foo(returnsFirstArg())) - person Lu55; 18.05.2015
comment
Винаги трябва да търся в Google този отговор отново и отново и отново през последните години, тъй като просто не мога да си спомня AdditionalAnswers и просто ми трябва много рядко. Тогава се чудя как, по дяволите, мога да създам този сценарий, тъй като не мога да намеря необходимите зависимости. Не може ли това просто да се добави директно към mockito? :/ - person BAERUS; 06.06.2016
comment
Отговорът на Стив е по-общ. Този ви позволява само да върнете необработения аргумент. Ако искате да обработите този аргумент и да върнете резултата, тогава правилата за отговор на Стив. Гласувах и за двете, тъй като и двете са полезни. - person akostadinov; 06.12.2017
comment
За информация, трябва да импортираме static org.mockito.AdditionalAnswers.returnsFirstArg. това за използване returnsFirstArg. Освен това мога да направя when(myMock.myFunction(any())).then(returnsFirstArg()) в Mockito 2.20.* - person gtiwari333; 30.07.2018
comment
Получавам грешка по време на компилиране Тип несъответствие: не може да се преобразува от Answer‹Object› в Collection‹MyObject›. Може би returnFirstArg не работи за генерични продукти? - person Curtis Yallop; 05.10.2018
comment
@CurtisYallop Сигурен ли си, че си разбрал правилно синтаксиса? Използвате ли doAnswer или then вместо doReturn или thenReturn? Имате ли скобите точно на правилните места? - person Dawood ibn Kareem; 07.10.2018

С Java 8 е възможно да се създаде отговор от един ред дори с по-стара версия на Mockito:

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

Разбира се, това не е толкова полезно, колкото използването на AdditionalAnswers, предложено от Дейвид Уолъс, но може да бъде полезно, ако искате да трансформирате аргумента "в движение".

person Paweł Dyda    schedule 04.02.2015
comment
Брилянтен. Благодаря ти. Ако аргументът е long, това все още може да работи с бокс и Long.class? - person vikingsteve; 01.02.2016
comment
.getArgumentAt(..) не беше намерен за мен, но .getArgument(1) работи (mockito 2.6.2) - person Curtis Yallop; 05.10.2018

Имах много подобен проблем. Целта беше да се подиграе с услуга, която запазва обекти и може да ги върне по името им. Услугата изглежда така:

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"));
person migu    schedule 19.07.2011

С 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"));
}

РЕДАКТИРАНЕ: Още по-кратко:

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"));
}
person yiwei    schedule 24.07.2015
comment
Това е страхотно, но не работи за thenThrow, за съжаление (thenThrow не приема аргумент InvocationOnMock). - person Dmitriy Popov; 22.07.2020

Това е доста стар въпрос, но мисля, че все още е актуален. Също така приетият отговор работи само за 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;
    }  
}
person LazR    schedule 28.02.2019

Това е малко старо, но дойдох тук, защото имах същия проблем. Използвам JUnit, но този път в приложение на Kotlin с mockk. Публикувам пример тук за справка и сравнение с аналога на 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"))
}
person Lachezar Balev    schedule 31.10.2019

Можете да постигнете това, като използвате 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());

Резюме: Използвайте argumentcaptor, за да заснемете предадения параметър. По-късно в отговор връща стойността, уловена с getValue.

person Cyril Cherian    schedule 01.11.2019
comment
Това не работи (вече?). По отношение на документите: Този метод трябва да се използва вътре в проверката. Това означава, че можете да заснемете стойността само когато използвате метода за проверка - person Muhammed Misir; 10.02.2020
comment
1. Не съм сигурен какво имате предвид под This doesn´t work (anymore?). Това работи на моя екземпляр. 2. Съжалявам, не ми е ясно какво се опитвате да кажете. Отговорът е специфичен за въпроса на OP. - person Cyril Cherian; 11.02.2020

Може да искате да използвате verify() в комбинация с ArgumentCaptor, за да осигурите изпълнение на теста и ArgumentCaptor, за да оцените аргументите:

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

Стойността на аргумента очевидно е достъпна чрез argument.getValue() за по-нататъшно манипулиране / проверка / каквото и да е.

person fl0w    schedule 28.10.2016

Използвам нещо подобно (по принцип това е същият подход). Понякога е полезно макетният обект да връща предварително дефиниран изход за определени входове. Това става така:

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
               }
           }
       }
       );
person martin    schedule 29.11.2010