Wenn Sie eine verspottete Methode erstellen, wird ein Argument zurückgegeben, das an sie übergeben wurde

673

Betrachten Sie eine Methodensignatur wie:

public String myFunction(String abc);

Kann Mockito dabei helfen, dieselbe Zeichenfolge zurückzugeben, die die Methode empfangen hat?

Abhijeet Kashnia
quelle
Ok, wie wäre es mit einem Java-Mocking-Framework im Allgemeinen ... Ist dies mit jedem anderen Framework möglich, oder sollte ich einfach einen dummen Stub erstellen, um das gewünschte Verhalten nachzuahmen?
Abhijeet Kashnia

Antworten:

1000

Sie können eine Antwort in Mockito erstellen. Nehmen wir an, wir haben eine Schnittstelle namens Application mit der Methode myFunction.

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

Hier ist die Testmethode mit einer Mockito-Antwort:

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

Seit Mockito 1.9.5 und Java 8 können Sie auch einen Lambda-Ausdruck verwenden:

when(myMock.myFunction(anyString())).thenAnswer(i -> i.getArguments()[0]);
Steve
quelle
1
Das habe ich auch gesucht. Vielen Dank! Mein Problem war jedoch anders. Ich möchte einen Persistenzdienst (EJB) verspotten, der Objekte speichert und sie nach Namen zurückgibt.
Migu
7
Ich habe eine zusätzliche Klasse erstellt, die die Erstellung der Antwort umschließt. Der Code lautet also wie when(...).then(Return.firstParameter())
folgt:
69
Mit Java 8 Lambdas ist es einfach, das erste Argument zurückzugeben, selbst für eine bestimmte Klasse, d when(foo(any()).then(i -> i.getArgumentAt(0, Bar.class)). H. Sie können auch eine Methodenreferenz verwenden und eine echte Methode aufrufen.
Paweł Dyda
Dies löst mein Problem mit einer Methode, die Iterator<? extends ClassName>alle Arten von Cast-Problemen in einer thenReturn()Anweisung verursacht.
Michael Shopsin
16
Mit Java 8 und Mockito <1.9.5 wird Pawełs Antwortwhen(foo(any()).thenAnswer(i -> i.getArguments()[0])
Graeme Moss
565

Wenn Sie Mockito 1.9.5 oder höher haben, gibt es eine neue statische Methode, mit der AnswerSie das Objekt für Sie erstellen können . Sie müssen so etwas schreiben

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

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

oder alternativ

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

Beachten Sie, dass die returnsFirstArg()Methode in der AdditionalAnswersKlasse statisch ist , die für Mockito 1.9.5 neu ist. Sie benötigen also den richtigen statischen Import.

Dawood ibn Kareem
quelle
17
Hinweis: Es ist when(...).then(returnsFirstArg()), ich hatte fälschlicherweise, when(...).thenReturn(returnsFirstArg())was gabjava.lang.ClassCastException: org.mockito.internal.stubbing.answers.ReturnsArgumentAt cannot be cast to
Benedikt Köppel
1
Hinweis: returnFirstArg () gibt Antwort <> und nicht den Wert des Arguments zurück. Beim Versuch, .thenReturn (new Foo (returnFirstArg ()))
Lu55
Ich muss diese Antwort in den letzten Jahren immer wieder googeln, da ich mich einfach nicht an "AdditionalAnswers" erinnern kann und sie nur sehr selten brauche. Dann frage ich mich, wie zum Teufel ich dieses Szenario aufbauen kann, da ich die notwendigen Abhängigkeiten nicht finden kann. Könnte dies nicht einfach direkt zu mockito hinzugefügt werden? : /
BAERUS
2
Steves Antwort ist allgemeiner. In diesem Fall können Sie nur das Rohargument zurückgeben. Wenn Sie dieses Argument verarbeiten und das Ergebnis zurückgeben möchten, gelten die Antwortregeln von Steve. Ich habe beide positiv bewertet, da beide nützlich sind.
Akostadinov
Zu Ihrer Information, wir müssen importieren static org.mockito.AdditionalAnswers.returnsFirstArg. Dies wird verwendet, um returnFirstArg zu verwenden. Auch kann ich when(myMock.myFunction(any())).then(returnsFirstArg())in Mockito 2.20 tun . *
gtiwari333
77

Mit Java 8 ist es möglich, auch mit einer älteren Version von Mockito eine einzeilige Antwort zu erstellen:

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

Dies ist natürlich nicht so nützlich wie die AdditionalAnswersvon David Wallace vorgeschlagene, kann aber nützlich sein, wenn Sie das Argument "on the fly" transformieren möchten.

Paweł Dyda
quelle
1
Brillant. Vielen Dank. Wenn das Argument ist long, kann dies immer noch mit Boxen und funktionieren Long.class?
Wikingersteve
1
.getArgumentAt (..) wurde nicht für mich gefunden, aber .getArgument (1) hat funktioniert (mockito 2.6.2)
Curtis Yallop
41

Ich hatte ein sehr ähnliches Problem. Das Ziel war es, einen Dienst zu verspotten, der Objekte beibehält und sie mit ihrem Namen zurückgeben kann. Der Service sieht folgendermaßen aus:

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

Der Service-Mock verwendet eine Karte zum Speichern der Rauminstanzen.

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

Wir können jetzt unsere Tests mit diesem Modell durchführen. Zum Beispiel:

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

Mit Java 8 kann Steves Antwort werden

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: Noch kürzer:

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"));
}
Yiwei
quelle
6

Dies ist eine ziemlich alte Frage, aber ich denke immer noch relevant. Auch die akzeptierte Antwort funktioniert nur für String. Inzwischen gibt es Mockito 2.1 und einige Importe haben sich geändert, daher möchte ich meine aktuelle Antwort teilen:

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());

Die myClass.myFunction würde folgendermaßen aussehen:

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

Ich benutze etwas Ähnliches (im Grunde ist es der gleiche Ansatz). Manchmal ist es nützlich, wenn ein Scheinobjekt für bestimmte Eingaben eine vordefinierte Ausgabe zurückgibt. Das geht so:

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
               }
           }
       }
       );
Martin
quelle
4

Möglicherweise möchten Sie verify () in Kombination mit dem ArgumentCaptor verwenden, um die Ausführung im Test sicherzustellen, und dem ArgumentCaptor, um die Argumente auszuwerten:

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

Der Wert des Arguments ist offensichtlich über argument.getValue () für weitere Manipulationen / Überprüfungen / was auch immer zugänglich.

fl0w
quelle
3

Das ist ein bisschen alt, aber ich bin hierher gekommen, weil ich das gleiche Problem hatte. Ich benutze JUnit aber diesmal in einer Kotlin App mit Mockk. Ich poste hier ein Beispiel als Referenz und Vergleich mit dem Java-Gegenstück:

@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"))
}
Lachezar Balev
quelle
2

Sie können dies mit ArgumentCaptor erreichen

Stellen Sie sich vor, Sie haben eine solche Bohnenfunktion.

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

Dann in Ihrer Testklasse:

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

ODER wenn Sie Lambda-Fan sind, tun Sie einfach:

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


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

Zusammenfassung: Verwenden Sie argumentcaptor, um den übergebenen Parameter zu erfassen. Geben Sie später in der Antwort den mit getValue erfassten Wert zurück.

Cyril Cherian
quelle
Das funktioniert nicht mehr (?). In Bezug auf die Dokumente: Diese Methode muss innerhalb der Überprüfung verwendet werden. Das heißt, Sie können den Wert nur erfassen, wenn Sie die Überprüfungsmethode verwenden
Muhammed Misir
1. This doesn´t work (anymore?).Ich bin mir nicht sicher, was du damit meinst. Ich arbeite an meiner Instanz. 2. Entschuldigung, ich bin mir nicht sicher, welchen Punkt Sie anstreben. Die Antwort ist spezifisch für die Frage von OP.
Cyril Cherian