Fazendo um método simulado retornar um argumento que foi passado para ele

673

Considere uma assinatura de método como:

public String myFunction(String abc);

O Mockito pode ajudar a retornar a mesma string que o método recebeu?

Abhijeet Kashnia
fonte
Ok, que tal qualquer estrutura de zombaria de java em geral ... Isso é possível com qualquer outra estrutura ou devo apenas criar um esboço idiota para imitar o comportamento que eu quero?
Abhijeet Kashnia

Respostas:

1000

Você pode criar uma resposta no Mockito. Vamos supor, temos uma interface chamada Application com o método myFunction.

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

Aqui está o método de teste com uma resposta do 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"));
}

Desde o Mockito 1.9.5 e o Java 8, você também pode usar uma expressão lambda:

when(myMock.myFunction(anyString())).thenAnswer(i -> i.getArguments()[0]);
Steve
fonte
1
Também era o que eu estava procurando. Obrigado! Meu problema era diferente, no entanto. Quero simular um serviço de persistência (EJB) que armazena objetos e os retorna por nome.
22711
7
Eu criei uma classe extra que envolve a criação da resposta. Portanto, o código diz comowhen(...).then(Return.firstParameter())
SpaceTrucker
69
Com o Java 8 lambdas, é fácil retornar o primeiro argumento para a ceia, mesmo para classes específicas when(foo(any()).then(i -> i.getArgumentAt(0, Bar.class)). E você também pode usar uma referência de método e chamar método real.
Paweł Dyda
Isso resolve meu problema com um método retornos Iterator<? extends ClassName>que causa todos os tipos de problemas de conversão em uma thenReturn()instrução.
precisa
16
Com Java 8 e Mockito <1.9.5, a resposta de Paweł se tornawhen(foo(any()).thenAnswer(i -> i.getArguments()[0])
Graeme Moss
566

Se você possui o Mockito 1.9.5 ou superior, existe um novo método estático que pode criar o Answerobjeto para você. Você precisa escrever algo como

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

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

ou alternativamente

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

Observe que o returnsFirstArg()método é estático na AdditionalAnswersclasse, que é nova no Mockito 1.9.5; então você precisará da importação estática correta.

Dawood ibn Kareem
fonte
17
Nota: é when(...).then(returnsFirstArg()), eu equivocadamente tinha when(...).thenReturn(returnsFirstArg())que deujava.lang.ClassCastException: org.mockito.internal.stubbing.answers.ReturnsArgumentAt cannot be cast to
Benedikt Köppel
1
Nota: ReturnsFirstArg () retorna a resposta <> em vez do valor do argumento. Got 'Foo (java.lang.String) não pode ser aplicado a' (org.mockito.stubbing.Answer <java.lang.Object>) 'ao tentar chamar .thenReturn (new Foo (ReturnsFirstArg ()))
Lu55
Eu sempre preciso pesquisar no Google esta resposta várias vezes nos últimos anos, pois não consigo me lembrar de "AdditionalAnswers" e só preciso muito raramente. Então me pergunto como diabos eu posso criar esse cenário, pois não consigo encontrar as dependências necessárias. Não foi possível adicionar isso diretamente ao mockito? : /
BAERUS 06/06
2
A resposta de Steve é ​​mais genérica. Este apenas permite retornar o argumento bruto. Se você deseja processar esse argumento e retornar o resultado, as regras da resposta de Steve. Eu votei em ambos, pois ambos são úteis.
Akostadinov
Para sua informação, temos que importar static org.mockito.AdditionalAnswers.returnsFirstArg. isso para usar ReturnsFirstArg. Além disso, eu posso fazer when(myMock.myFunction(any())).then(returnsFirstArg())no Mockito 2.20. *
gtiwari333 30/07
77

Com o Java 8, é possível criar uma resposta de uma linha mesmo com a versão mais antiga do Mockito:

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

Claro que isso não é tão útil quanto o uso AdditionalAnswerssugerido por David Wallace, mas pode ser útil se você quiser transformar o argumento "on the fly".

Paweł Dyda
fonte
1
Brilhante. Obrigado. Se o argumento for long, isso ainda pode funcionar com boxe e Long.class?
vikingsteve
1
.getArgumentAt (..) não foi encontrado para mim, mas o .getArgument (1) funcionou (mockito 2.6.2)
Curtis Yallop
41

Eu tive um problema muito parecido. O objetivo era zombar de um serviço que persiste em Objetos e pode devolvê-los pelo nome. O serviço fica assim:

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

A simulação de serviço usa um mapa para armazenar as instâncias da sala.

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

Agora podemos executar nossos testes com essa simulação. Por exemplo:

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

Com o Java 8, a resposta de Steve pode se tornar

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: Ainda mais curto:

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
fonte
6

Esta é uma pergunta bastante antiga, mas acho que ainda é relevante. Além disso, a resposta aceita funciona apenas para String. Enquanto isso, há o Mockito 2.1 e algumas importações foram alteradas, então eu gostaria de compartilhar minha resposta atual:

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

O myClass.myFunction seria semelhante a:

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

Eu uso algo semelhante (basicamente é a mesma abordagem). Às vezes, é útil fazer com que um objeto simulado retorne uma saída predefinida para determinadas entradas. É assim:

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
fonte
4

Convém usar o Checker () em combinação com o ArgumentCaptor para garantir a execução no teste e o ArgumentCaptor para avaliar os argumentos:

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

O valor do argumento é obviamente acessível através do argument.getValue () para manipulação / verificação adicional / o que for.

fl0w
fonte
3

Isso é um pouco antigo, mas eu vim aqui porque tinha o mesmo problema. Estou usando o JUnit, mas desta vez em um aplicativo Kotlin com mockk. Estou postando uma amostra aqui para referência e comparação com a contraparte 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
fonte
2

Você pode conseguir isso usando ArgumentCaptor

Imagine que você tem a função de bean assim.

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

Então na sua classe de teste:

//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 se você é um fã de lambda, faça:

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


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

Resumo: Use o argumentocaptor para capturar o parâmetro passado. Posteriormente, em resposta, retorne o valor capturado usando getValue.

Cyril Cherian
fonte
Isso não funciona (mais?). Em relação aos documentos: Este método deve ser usado dentro da verificação. Isso significa que você só pode capturar o valor ao usar o método de verificação
Muhammed Misir
1. Não sei ao certo o que você quer dizer com This doesn´t work (anymore?).isso funcionando na minha instância. 2. Desculpe, não sei ao certo o que você está tentando fazer. A resposta é específica para a pergunta do OP.
Cyril Cherian