Você pode me ajudar a entender o Moq Callback?

95

Usando o Moq e olhei, Callbackmas não consegui encontrar um exemplo simples para entender como usá-lo.

Você tem um pequeno trecho de trabalho que explica claramente como e quando usá-lo?

usuário9969
fonte

Respostas:

83

Difícil de vencer https://github.com/Moq/moq4/wiki/Quickstart

Se isso não estiver claro o suficiente, eu chamaria isso de bug de doc ...

EDITAR: Em resposta ao seu esclarecimento ...

Para cada método simulado que Setupvocê executa, pode indicar coisas como:

  • restrições nas entradas
  • o valor para / forma em que o valor de retorno (se houver) deve ser derivado

O .Callbackmecanismo diz "Não consigo descrever agora, mas quando uma chamada com esse formato acontecer, me ligue de volta e farei o que for preciso". Como parte da mesma cadeia de chamadas fluente, você controla o resultado a ser retornado (se houver) por meio de .Returns". Nos exemplos de QS, um exemplo é que eles fazem o valor retornado aumentar a cada vez.

Em geral, você não precisará de um mecanismo como este com muita frequência (os padrões de teste xUnit têm termos para antipadrões do tipo Lógica condicional em testes) e, se houver alguma maneira mais simples ou integrada de estabelecer o que você precisa, deve ser usado de preferência.

A parte 3 de 4 da série Moq de Justin Etheredge cobre isso, e há outro exemplo de callbacks aqui

Um exemplo simples de callback pode ser encontrado em Using Callbacks with Moq post.

Ruben Bartelink
fonte
3
Olá, Ruben. Estou aprendendo o Moq e, se você gosta, estou compilando muitos exemplos para entender como fazer as coisas usando-o. Meu problema é que não entendo quando usá-lo. Assim que entender o problema resolvido, escreverei meu próprio código. Se você fosse explicá-lo com suas próprias palavras, quando usaria o retorno de chamada? obrigado, aprecio seu tempo
user9969
15
Difícil de bater [link]? De modo nenhum. Esse link mostra como fazer dezenas de coisas diferentes, mas não diz por que você precisa fazer qualquer uma delas. Que é um problema comum em documentação de simulação, eu descobri. Posso contar com zero dedos o número de explicações boas e claras de zombaria TDD + que encontrei. A maioria assume um nível de conhecimento que, se eu o tivesse, não precisaria ler o artigo.
Ryan Lundy,
@Kyralessa: Entendo seu ponto. Eu, pessoalmente, tinha bastante conhecimento sobre livros, então achei o guia de início rápido absolutamente perfeito. Infelizmente, não conheço um exemplo melhor do que os que vinculei no final do post. Se você encontrar um, poste aqui e terei o maior prazer em editá-lo em (ou sinta-se à vontade para fazer você mesmo)
Ruben Bartelink
"Vou fazer o que precisa ser feito e dizer a você o resultado a retornar (se houver)" Acho que isso é enganoso, AFAIU Callbacknão tem nada a ver com o valor de retorno (a menos que você o vincule por meio de código). Basicamente, ele apenas garante que o callback seja chamado antes ou depois de cada invocação (dependendo se você o encadeou antes ou depois, Returnsrespectivamente), puro e simples.
Ohad Schneider de
1
@OhadSchneider Seguindo meu link ... você está correto! Gostaria de saber (mas não estou realmente interessado o suficiente, pois não usei Moq por muuuuito tempo) se a interface do Fluent mudou (não parece provável, ou seja, eu fiz uma suposição errada e não li o link ao qual eu normalmente pensaria de autocompletar eu mesmo). Espero que a correção aborde seu ponto, me avise se não
resolver
59

Aqui está um exemplo de como usar um retorno de chamada para testar uma entidade enviada a um serviço de dados que lida com uma inserção.

var mock = new Mock<IDataService>();
DataEntity insertedEntity = null;

mock.Setup(x => x.Insert(It.IsAny<DataEntity>())).Returns(1) 
           .Callback((DataEntity de) => insertedEntity = de);

Sintaxe do método genérico alternativo:

mock.Setup(x => x.Insert(It.IsAny<DataEntity>())).Returns(1) 
           .Callback<DataEntity>(de => insertedEntity = de);

Então você pode testar algo como

Assert.AreEqual("test", insertedEntity.Description, "Wrong Description");
Jeff Hall
fonte
4
Indiscutivelmente para aquele caso específico (dependendo se você está tentando expressar testes contra estado ou comportamento), em alguns casos pode ser mais limpo usar um It.Is<T>em a em Mock.Verifyvez de sujar o teste com temporários. Mas +1 porque aposto que há muitas pessoas que trabalharão melhor com um exemplo.
Ruben Bartelink
10

Existem dois tipos de Callbackno Moq. Um acontece antes que a chamada retorne; o outro acontece após o retorno da chamada.

var message = "";
mock.Setup(foo => foo.Execute(arg1: "ping", arg2: "pong"))
    .Callback((x, y) =>
    {
        message = "Rally on!";
        Console.WriteLine($"args before returns {x} {y}");
    })
    .Returns(message) // Rally on!
    .Callback((x, y) =>
    {
        message = "Rally over!";
        Console.WriteLine("arg after returns {x} {y}");
    });

Em ambos os retornos de chamada, podemos:

  1. inspecionar argumentos de método
  2. argumentos do método de captura
  3. mudar o estado contextual
Shaun Luttin
fonte
2
Na verdade, ambos acontecem antes do retorno da chamada (no que diz respeito ao chamador). Consulte stackoverflow.com/a/28727099/67824 .
Ohad Schneider de
5

Callbacké simplesmente um meio de executar qualquer código personalizado que você deseja quando uma chamada é feita para um dos métodos do mock. Aqui está um exemplo simples:

public interface IFoo
{
    int Bar(bool b);
}

var mock = new Mock<IFoo>();

mock.Setup(mc => mc.Bar(It.IsAny<bool>()))
    .Callback<bool>(b => Console.WriteLine("Bar called with: " + b))
    .Returns(42);

var ret = mock.Object.Bar(true);
Console.WriteLine("Result: " + ret);

// output:
// Bar called with: True
// Result: 42

Recentemente, encontrei um caso de uso interessante para ele. Suponha que você espere algumas chamadas para sua simulação, mas elas acontecem simultaneamente. Portanto, você não tem como saber a ordem em que eles seriam chamados, mas deseja saber as chamadas que esperava que ocorressem (independentemente da ordem). Você pode fazer algo assim:

var cq = new ConcurrentQueue<bool>();
mock.Setup(f => f.Bar(It.IsAny<bool>())).Callback<bool>(cq.Enqueue);
Parallel.Invoke(() => mock.Object.Bar(true), () => mock.Object.Bar(false));
Console.WriteLine("Invocations: " + String.Join(", ", cq));

// output:
// Invocations: True, False

BTW, não se confunda com a enganosa distinção "antes Returns" e "depois Returns". É meramente uma distinção técnica se o seu código personalizado será executado após a Returnsavaliação ou antes. Aos olhos do chamador, ambos serão executados antes que o valor seja retornado. Na verdade, se o método for void-returning, você não pode nem chamar Returnse ainda funciona da mesma forma. Para obter mais informações, consulte https://stackoverflow.com/a/28727099/67824 .

Ohad Schneider
fonte
1

Além das outras boas respostas aqui, usei-o para realizar a lógica antes de lançar uma exceção. Por exemplo, eu precisava armazenar todos os objetos que foram passados ​​para um método para verificação posterior, e esse método (em alguns casos de teste) precisava lançar uma exceção. Chamando .Throws(...)no Mock.Setup(...)substitui a Callback()ação e nunca chama. No entanto, ao lançar uma exceção dentro do Callback, você ainda pode fazer todas as coisas boas que um callback tem a oferecer e ainda lançar uma exceção.

Frank Bryce
fonte