Como funciona a invocação mockito when ()?

111

Dada a seguinte declaração Mockito:

when(mock.method()).thenReturn(someValue);

Como o Mockito cria um proxy para um mock, visto que a instrução mock.method () passará o valor de retorno para when ()? Eu imagino que isso use algumas coisas CGLib, mas gostaria de saber como isso é feito tecnicamente.

marchaos
fonte

Respostas:

118

A resposta curta é que em seu exemplo, o resultado de mock.method()será um valor vazio apropriado ao tipo; mockito usa indireção via proxy, interceptação de método e uma instância compartilhada da MockingProgressclasse a fim de determinar se uma invocação de um método em um mock é para stubbing ou repetição de um comportamento de stub existente em vez de passar informações sobre stub por meio do valor de retorno de um método simulado.

Uma mini-análise em alguns minutos examinando o código do mockito é a seguinte. Observe, esta é uma descrição muito aproximada - há muitos detalhes em jogo aqui. Eu sugiro que você verifique a fonte no github .

Primeiro, quando você simula uma classe usando o mockmétodo da Mockitoclasse, é essencialmente o que acontece:

  1. Mockito.mockdelegados para org.mockito.internal.MockitoCore.mock, passando as configurações de simulação padrão como um parâmetro.
  2. MockitoCore.mockdelegados para org.mockito.internal.util.MockUtil.createMock
  3. A MockUtilclasse usa a ClassPathLoaderclasse para obter uma instância de MockMakerpara usar para criar a simulação. Por padrão, a classe CgLibMockMaker é usada.
  4. CgLibMockMakerusa uma classe emprestada do JMock, ClassImposterizerque lida com a criação do mock. As peças-chave da 'magia mockito' usadas são as MethodInterceptorusadas para criar o mockito: o mockito MethodInterceptorFiltere uma cadeia de instâncias de MockHandler, incluindo uma instância de MockHandlerImpl . O interceptor de método passa invocações para a instância MockHandlerImpl, que implementa a lógica de negócios que deve ser aplicada quando um método é invocado em uma simulação (ou seja, procurando ver se uma resposta já foi gravada, determinando se a invocação representa um novo stub, etc. O estado padrão é que se um stub ainda não estiver registrado para o método que está sendo chamado, um valor vazio apropriado ao tipo é retornado.

Agora, vamos examinar o código em seu exemplo:

when(mock.method()).thenReturn(someValue)

Esta é a ordem em que este código será executado:

  1. mock.method()
  2. when(<result of step 1>)
  3. <result of step 2>.thenReturn

A chave para entender o que está acontecendo é o que acontece quando o método no mock é invocado: o interceptor do método recebe informações sobre a invocação do método e delega para sua cadeia de MockHandlerinstâncias, que eventualmente delega para MockHandlerImpl#handle. Durante MockHandlerImpl#handle, o manipulador de simulação cria uma instância de OngoingStubbingImple a passa para a MockingProgressinstância compartilhada .

Quando o whenmétodo é invocado após a invocação de method(), ele delega para MockitoCore.when, que chama o stub()método da mesma classe. Este método descompacta o stub em andamento da MockingProgressinstância compartilhada em que a method()invocação simulada escreveu e o retorna. Então, o thenReturnmétodo é então chamado na OngoingStubbinginstância.

Paul Morie
fonte
1
Obrigado pela resposta detalhada. Outra pergunta - você menciona que "quando o método é invocado após a invocação de method ()" - como ele sabe que a invocação de when () é a próxima invocação (ou quebra) a invocação de method ()? Espero que isso faça sentido.
marchaos
@marchaos Não sabe. Com a when(mock.method()).thenXyz(...)sintaxe, mock.method()é executado no modo "replay", não no modo "stubbing". Normalmente, esta execução mock.method()não tem nenhum efeito, então mais tarde quando thenXyz(...)( thenReturn, thenThrow, thenAnswer, etc) é executado, ele vai para "arrancar" o modo e, em seguida, grava o resultado desejado para essa chamada de método.
Rogério
1
Rogerio, na verdade é um pouco mais sutil do que isso - mockito não tem modos de stub e replay explícitos. Vou editar minha resposta mais tarde para ficar mais clara.
Paul Morie
Eu resumiria que, em resumo, é mais fácil interceptar uma chamada de método dentro de outro método com CGLIB ou Javassist do que interceptar, digamos, o operador "if".
Infeligo
Ainda não fiz uma massagem na minha descrição aqui, mas também não a esqueci. PARA SUA INFORMAÇÃO.
Paul Morie
33

A resposta curta é, nos bastidores, o Mockito usa algum tipo de variável / armazenamento global para salvar informações das etapas de construção de stub do método (invocação do método (), when (), thenReturn () em seu exemplo), para que eventualmente possa construir um mapa sobre o que deve ser retornado quando o que é chamado em qual parâmetro.

Achei este artigo muito útil: Explicação de como funcionam os Mock Frameworks baseados em proxy ( http://blog.rseiler.at/2014/06/explanation-how-proxy-based-mock.html ). O autor implementou uma estrutura de simulação de demonstração, que achei um recurso muito bom para pessoas que desejam descobrir como essas estruturas de simulação funcionam.

Na minha opinião, é um uso típico do Anti-Pattern. Normalmente devemos evitar o 'efeito colateral' quando implementamos um método, o que significa que o método deve aceitar a entrada e fazer alguns cálculos e retornar o resultado - nada mais mudou além disso. Mas Mockito apenas viola essa regra de propósito. Seus métodos armazenam um monte de informações além de retornar o resultado: Mockito.anyString (), mockInstance.method (), when (), thenReturn, todos eles têm um 'efeito colateral' especial. É também por isso que o framework parece mágico à primeira vista - geralmente não escrevemos código assim. No entanto, no caso da estrutura de simulação, esse design de antipadrão é um ótimo design, pois leva a uma API muito simples.

Jacob Wu
fonte
4
Excelente link. O gênio por trás disso é: A API muito simples que faz com que tudo pareça muito bom. Outra grande decisão é que o método when () usa genéricos para que o método thenReturn () seja seguro para o tipo.
David Tonhofer,
Eu considero esta a melhor resposta. Em contraste com a outra resposta, ela explica claramente os conceitos do processo de simulação em vez do fluxo de controle por meio do código concreto. Eu concordo, excelente link.
mihca