Fare un metodo deriso restituisce un argomento che gli è stato passato


675

Prendi in considerazione una firma del metodo come:

public String myFunction(String abc);

Mockito può aiutare a restituire la stessa stringa ricevuta dal metodo?


Ok, che ne dici di qualsiasi framework jocking in generale ... È possibile con qualsiasi altro framework, o dovrei semplicemente creare uno stub stupido per imitare il comportamento che desidero?
Abhijeet Kashnia,

Risposte:


1001

Puoi creare una risposta in Mockito. Supponiamo che abbiamo un'interfaccia denominata Applicazione con un metodo myFunction.

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

Ecco il metodo di prova con una risposta 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"));
}

Da Mockito 1.9.5 e Java 8, puoi anche usare un'espressione lambda:

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

1
Questo è quello che stavo anche cercando. Grazie! Il mio problema era diverso, però. Voglio deridere un servizio di persistenza (EJB) che memorizza gli oggetti e li restituisce per nome.
migu,

7
Ho creato una classe extra che avvolge la creazione della risposta. Quindi il codice dicewhen(...).then(Return.firstParameter())
SpaceTrucker il

69
Con Java 8 lambdas è facile per la cena restituire il primo argomento, anche per una classe specifica, ad es when(foo(any()).then(i -> i.getArgumentAt(0, Bar.class)). E puoi anche usare un riferimento al metodo e chiamare il metodo reale.
Paweł Dyda,

Questo risolve il mio problema con un metodo i rendimenti Iterator<? extends ClassName>che causano tutti i tipi di problemi di cast in thenReturn()un'istruzione.
Michael Shopsin

16
Con Java 8 e Mockito <1.9.5, la risposta di Paweł diventawhen(foo(any()).thenAnswer(i -> i.getArguments()[0])
Graeme Moss,

567

Se hai Mockito 1.9.5 o versioni successive, esiste un nuovo metodo statico che può creare l' Answeroggetto per te. Devi scrivere qualcosa del genere

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

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

o in alternativa

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

Si noti che il returnsFirstArg()metodo è statico nella AdditionalAnswersclasse, che è nuovo in Mockito 1.9.5; quindi avrai bisogno della giusta importazione statica.


17
Nota: è when(...).then(returnsFirstArg()), ho sbagliato per sbaglio when(...).thenReturn(returnsFirstArg())che ha datojava.lang.ClassCastException: org.mockito.internal.stubbing.answers.ReturnsArgumentAt cannot be cast to
Benedikt Köppel l'

1
Nota: ReturnsFirstArg () restituisce la risposta <> anziché il valore dell'argomento. Got 'Foo (java.lang.String) non può essere applicato a' (org.mockito.stubbing.Answer <java.lang.Object>) 'durante il tentativo di chiamare .thenReturn (nuovo Foo (ReturnsFirstArg ()))
Lu55

Devo sempre cercare su Google questa risposta ancora e ancora e ancora negli ultimi anni, dato che non riesco a ricordare "AdditionalAnswers" e ne ho solo bisogno molto raramente. Quindi mi chiedo come diavolo posso costruire quello scenario perché non riesco a trovare le dipendenze necessarie. Non è possibile aggiungerlo direttamente a mockito? : /
BAERUS

2
La risposta di Steve è più generica. Questo ti consente solo di restituire l'argomento grezzo. Se vuoi elaborare quell'argomento e restituire il risultato, allora le regole di risposta di Steve. Ho votato entrambi perché sono entrambi utili.
Akostadinov,

Cordiali saluti, dobbiamo importare static org.mockito.AdditionalAnswers.returnsFirstArg. questo per usare ReturnsFirstArg. Inoltre, posso fare when(myMock.myFunction(any())).then(returnsFirstArg())in Mockito 2.20. *
gtiwari333

78

Con Java 8 è possibile creare una risposta di una riga anche con la versione precedente di Mockito:

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

Naturalmente questo non è utile come l'uso AdditionalAnswerssuggerito da David Wallace, ma potrebbe essere utile se si desidera trasformare l'argomento "al volo".


1
Brillante. Grazie. Se l'argomento è long, può ancora funzionare con la boxe e Long.class?
vikingsteve,

1
.getArgumentAt (..) non è stato trovato per me ma .getArgument (1) ha funzionato (mockito 2.6.2)
Curtis Yallop,

41

Ho avuto un problema molto simile. L'obiettivo era quello di deridere un servizio che persiste gli oggetti e può restituirli con il loro nome. Il servizio è simile al seguente:

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

Il servizio mock utilizza una mappa per memorizzare le istanze di 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;
    }
});

Ora possiamo eseguire i nostri test su questo finto. Per esempio:

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

34

Con Java 8, la risposta di Steve può diventare

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: Ancora più breve:

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

Questa è una domanda piuttosto vecchia ma ritengo sia ancora pertinente. Anche la risposta accettata funziona solo per String. Nel frattempo c'è Mockito 2.1 e alcune importazioni sono cambiate, quindi vorrei condividere la mia risposta attuale:

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 sarebbe simile a:

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

4

Uso qualcosa di simile (sostanzialmente è lo stesso approccio). A volte è utile che un oggetto simulato restituisca un output predefinito per determinati input. Va così:

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

È possibile che si desideri utilizzare verify () in combinazione con ArgumentCaptor per assicurare l'esecuzione nel test e ArgumentCaptor per valutare gli argomenti:

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

Il valore dell'argomento è ovviamente accessibile tramite argomento.getValue () per ulteriori manipolazioni / controlli / qualunque cosa.


3

Questo è un po 'vecchio, ma sono venuto qui perché avevo lo stesso problema. Sto usando JUnit ma questa volta in un'app Kotlin con mockk. Sto pubblicando un campione qui per riferimento e confronto con la controparte 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

È possibile ottenere ciò utilizzando ArgumentCaptor

Immagina di avere la funzione bean in questo modo.

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

Quindi nella tua classe di test:

//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.
    }
  });

O se sei un fan di Lambda, semplicemente:

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


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

Riepilogo: utilizzare argomentocaptor per acquisire il parametro passato. Successivamente nella risposta restituisce il valore acquisito usando getValue.


Questo non funziona (più?). Per quanto riguarda i documenti: questo metodo deve essere utilizzato all'interno della verifica. Ciò significa che è possibile acquisire il valore solo quando si utilizza il metodo di verifica
Muhammed Misir il

1. Non sono sicuro di cosa intendi con This doesn´t work (anymore?).questo lavoro sul mio caso. 2. Mi dispiace, non sono chiaro sul punto che stai cercando di chiarire. La risposta è specifica alla domanda di OP.
Cyril Cherian,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.