Faire en sorte qu'une méthode simulée renvoie un argument qui lui a été transmis

675

Considérez une signature de méthode comme:

public String myFunction(String abc);

Mockito peut-il aider à renvoyer la même chaîne que la méthode reçue?

Abhijeet Kashnia
la source
Ok, que diriez-vous de n'importe quel framework de moquage java en général ... Est-ce possible avec n'importe quel autre framework, ou devrais-je simplement créer un stub stupide pour imiter le comportement que je veux?
Abhijeet Kashnia

Réponses:

1001

Vous pouvez créer une réponse dans Mockito. Supposons que nous avons une interface nommée Application avec une méthode myFunction.

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

Voici la méthode de test avec une réponse 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"));
}

Depuis Mockito 1.9.5 et Java 8, vous pouvez également utiliser une expression lambda:

when(myMock.myFunction(anyString())).thenAnswer(i -> i.getArguments()[0]);
Steve
la source
1
C'est ce que je cherchais aussi. Je vous remercie! Mais mon problème était différent. Je veux me moquer d'un service de persistance (EJB) qui stocke des objets et les renvoie par nom.
migu
7
J'ai créé une classe supplémentaire qui encapsule la création de la réponse. Donc le code se lit commewhen(...).then(Return.firstParameter())
SpaceTrucker
69
Avec Java 8 lambdas, il est facile de retourner le premier argument, même pour une classe spécifique, c'est-à-dire when(foo(any()).then(i -> i.getArgumentAt(0, Bar.class)). Et vous pouvez tout aussi bien utiliser une référence de méthode et appeler une méthode réelle.
Paweł Dyda
Cela résout mon problème avec une méthode de retour Iterator<? extends ClassName>qui provoque toutes sortes de problèmes de transtypage dans une thenReturn()instruction.
Michael Shopsin
16
Avec Java 8 et Mockito <1.9.5, la réponse de Paweł devientwhen(foo(any()).thenAnswer(i -> i.getArguments()[0])
Graeme Moss
567

Si vous avez Mockito 1.9.5 ou supérieur, il existe une nouvelle méthode statique qui peut créer l' Answerobjet pour vous. Vous devez écrire quelque chose comme

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

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

Ou bien

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

Notez que la returnsFirstArg()méthode est statique dans la AdditionalAnswersclasse, ce qui est nouveau pour Mockito 1.9.5; vous aurez donc besoin de la bonne importation statique.

Dawood ibn Kareem
la source
17
Remarque: c'est when(...).then(returnsFirstArg()), j'ai eu par erreur when(...).thenReturn(returnsFirstArg())qui a donnéjava.lang.ClassCastException: org.mockito.internal.stubbing.answers.ReturnsArgumentAt cannot be cast to
Benedikt Köppel
1
Remarque: renvoieFirstArg () renvoie la réponse <> plutôt que la valeur de l'argument. Got 'Foo (java.lang.String) ne peut pas être appliqué à' (org.mockito.stubbing.Answer <java.lang.Object>) 'tout en essayant d'appeler .thenReturn (new Foo (ReturnsFirstArg ()))
Lu55
J'ai toujours besoin de google cette réponse encore et encore et encore au cours des dernières années, car je ne me souviens tout simplement pas "AdditionalAnswers" et j'en ai juste besoin très rarement. Ensuite, je me demande comment diable je peux construire ce scénario car je ne peux pas trouver les dépendances nécessaires. Ne pourrait-il pas simplement être ajouté directement à mockito? : /
BAERUS
2
La réponse de Steve est plus générique. Celui-ci vous permet uniquement de renvoyer l'argument brut. Si vous souhaitez traiter cet argument et renvoyer le résultat, alors la réponse de Steve est la règle. J'ai voté pour les deux car ils sont tous les deux utiles.
akostadinov
Pour info, nous devons importer static org.mockito.AdditionalAnswers.returnsFirstArg. ceci pour utiliser ReturnsFirstArg. Aussi, je peux le faire when(myMock.myFunction(any())).then(returnsFirstArg())dans Mockito 2.20. *
gtiwari333
78

Avec Java 8, il est possible de créer une réponse sur une seule ligne, même avec une ancienne version de Mockito:

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

Bien sûr, cela n'est pas aussi utile que l'utilisation AdditionalAnswerssuggérée par David Wallace, mais peut être utile si vous souhaitez transformer l'argument "à la volée".

Paweł Dyda
la source
1
Brillant. Je vous remercie. Si l'argument est long, cela peut-il encore fonctionner avec la boxe et Long.class?
vikingsteve
1
.getArgumentAt (..) n'a pas été trouvé pour moi mais .getArgument (1) a fonctionné (mockito 2.6.2)
Curtis Yallop
41

J'ai eu un problème très similaire. L'objectif était de se moquer d'un service qui persiste sur les objets et peut les renvoyer par leur nom. Le service ressemble à ceci:

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

La maquette de service utilise une carte pour stocker les instances de salle.

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

Nous pouvons maintenant exécuter nos tests sur cette maquette. Par exemple:

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

Avec Java 8, la réponse de Steve peut devenir

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: Encore plus court:

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
la source
6

C'est une question assez ancienne mais je pense qu'elle est toujours d'actualité. De plus, la réponse acceptée ne fonctionne que pour String. En attendant, il y a Mockito 2.1 et certaines importations ont changé, je voudrais donc partager ma réponse actuelle:

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

La fonction myClass.myFunction ressemblerait à:

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

J'utilise quelque chose de similaire (en gros c'est la même approche). Parfois, il est utile qu'un objet simulé renvoie une sortie prédéfinie pour certaines entrées. Cela se passe comme ceci:

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
la source
4

Vous souhaiterez peut-être utiliser verify () en combinaison avec ArgumentCaptor pour assurer l'exécution dans le test et ArgumentCaptor pour évaluer les arguments:

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

La valeur de l'argument est évidemment accessible via l'argument.getValue () pour une manipulation / vérification / autre.

fl0w
la source
3

C'est un peu vieux, mais je suis venu ici parce que j'avais le même problème. J'utilise JUnit mais cette fois dans une application Kotlin avec mockk. Je poste un échantillon ici pour référence et comparaison avec l'homologue 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"))
}
Lachezar Balev
la source
2

Vous pouvez y parvenir en utilisant ArgumentCaptor

Imaginez que vous ayez une fonction de bean comme ça.

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

Ensuite, dans votre classe de 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.
    }
  });

OU si vous êtes fan de lambda, faites simplement:

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


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

Résumé: utilisez argumentcaptor pour capturer le paramètre transmis. Plus tard dans la réponse, renvoyez la valeur capturée à l'aide de getValue.

Cyril Cherian
la source
Cela ne fonctionne plus (plus?). Concernant les documents: Cette méthode doit être utilisée à l'intérieur de la vérification. Cela signifie que vous ne pouvez capturer la valeur qu'en utilisant la méthode de vérification
Muhammed Misir
1. Je ne sais pas ce que vous entendez par ce que This doesn´t work (anymore?).je travaille sur mon instance. 2. Désolé, je ne suis pas clair sur le point que vous essayez de faire valoir. La réponse est spécifique à la question d'OP.
Cyril Cherian