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?


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

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.


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".


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

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

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

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

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.


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

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.


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
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.