Czy możesz mi pomóc zrozumieć Moq Callback?

99

Używając Moq i przyjrzałem się, Callbackale nie byłem w stanie znaleźć prostego przykładu, aby zrozumieć, jak go używać.

Czy masz mały działający fragment, który jasno wyjaśnia, jak i kiedy go używać?

user9969
źródło

Odpowiedzi:

85

Trudno pokonać https://github.com/Moq/moq4/wiki/Quickstart

Jeśli to nie jest wystarczająco jasne, nazwałbym to błędem dokumentacji ...

EDYCJA: W odpowiedzi na Twoje wyjaśnienie ...

W przypadku każdej Setupwykonywanej przez ciebie fałszywej metody możesz wskazać takie rzeczy, jak:

  • ograniczenia dotyczące danych wejściowych
  • wartość / sposób, w jaki wartość zwracana (jeśli istnieje) ma być wyprowadzona

.CallbackMechanizm mówi „nie mogę opisać to teraz, ale gdy połączenie kształcie to się dzieje, zadzwoń do mnie z powrotem i zrobię, co trzeba zrobić”. Jako część tego samego łańcucha płynnych wywołań, możesz kontrolować wynik do zwrócenia (jeśli istnieje) za pośrednictwem .Returns". W przykładach QS, przykładem jest to, że zwiększają one zwracaną wartość za każdym razem.

Ogólnie rzecz biorąc, nie będziesz potrzebował takiego mechanizmu bardzo często (wzorce testowe xUnit mają określenia dla antywzorów podobnych do testów warunkowej logiki w testach), a jeśli istnieje prostszy lub wbudowany sposób ustalenia potrzeb, powinien być używane w pierwszej kolejności.

Część 3 z 4 serii Moq Justina Etheredge'a obejmuje to, a tutaj jest inny przykład wywołań zwrotnych

Prosty przykład oddzwonienia można znaleźć na stronie Korzystanie z oddzwonienia z postem Moq .

Ruben Bartelink
źródło
4
Cześć Ruben Uczę się Moq i jeśli lubisz, buduję wiele przykładów, aby zrozumieć, jak robić rzeczy używając go. Mój problem polega na tym, że nie rozumiem, kiedy go użyć. Gdy zrozumiem, że problem został rozwiązany, napiszę własny kod. Gdybyś miał to wyjaśnić swoim własnym słowem, kiedy użyłbyś callback? dziękuję, doceniam twój czas
user9969
17
Trudno pobić [link]? Ani trochę. Ten link pokazuje, jak robić dziesiątki różnych rzeczy, ale nie mówi ci, dlaczego miałbyś to zrobić. Co jest częstym problemem przy kpieniu z dokumentacji. Mogę policzyć na zero palców liczbę dobrych, jasnych wyjaśnień TDD + kpiny, które znalazłem. Większość przyjmuje taki poziom wiedzy, że gdybym go miał, nie musiałbym czytać artykułu.
Ryan Lundy
@Kyralessa: Rozumiem. Osobiście miałem sporo wiedzy książkowej, więc stwierdziłem, że szybki start jest absolutnie doskonały. Niestety nie znam lepszego przykładu niż te, do których dołączyłem na końcu postu. Jeśli znajdziesz jakiś, opublikuj go tutaj, a ja z przyjemnością go wyedytuję (lub zrób to sam)
Ruben Bartelink
„Zrobię, co trzeba, i podam wynik do zwrócenia (jeśli w ogóle)”. Myślę, że to mylące, AFAIU Callbacknie ma nic wspólnego z wartością zwracaną (chyba że zdarzy ci się połączyć ją za pomocą kodu). Zasadniczo zapewnia tylko, że wywołanie zwrotne zostanie wywołane przed lub po każdym wywołaniu (w zależności od tego, czy połączyłeś je odpowiednio przed, czy po Returns), proste i proste.
Ohad Schneider
1
@OhadSchneider Podążając za moim linkiem ... masz rację! Zastanawiam się (ale niezbyt zainteresowany, ponieważ nie korzystałem z Moq przez długi czas), czy interfejs Fluent się zmienił (nie wydaje się prawdopodobne, tj. Zrobiłem błędne założenie i nie przeczytałem rzeczy, z którą się łączyłem, tak jak normalnie to rozwiązałem z autouzupełniania samodzielnie). Mam nadzieję, że poprawka rozwiązuje Twój punkt widzenia, daj mi znać, jeśli nie działa
Ruben Bartelink
60

Oto przykład użycia wywołania zwrotnego w celu przetestowania jednostki wysłanej do usługi danych, która obsługuje wstawianie.

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

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

Alternatywna składnia metody ogólnej:

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

Następnie możesz przetestować coś takiego

Assert.AreEqual("test", insertedEntity.Description, "Wrong Description");
Jeff Hall
źródło
4
Prawdopodobnie w tym konkretnym przypadku (w zależności od tego, czy próbujesz wyrazić testy w odniesieniu do stanu lub zachowania), w niektórych przypadkach może być czystsze użycie It.Is<T>w a Mock.Verifyzamiast zaśmiecania testu tymczasowymi. Ale +1, bo założę się, że na przykładzie wielu ludzi będzie działać najlepiej.
Ruben Bartelink
10

Istnieją dwa rodzaje Callbackw Moq. Jeden zdarza się przed powrotem połączenia; druga ma miejsce po powrocie połączenia.

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

W obu callbackach możemy:

  1. sprawdź argumenty metody
  2. przechwytywanie argumentów metody
  3. zmienić stan kontekstowy
Shaun Luttin
źródło
2
W rzeczywistości oba mają miejsce przed powrotem połączenia (jeśli chodzi o dzwoniącego). Zobacz stackoverflow.com/a/28727099/67824 .
Ohad Schneider,
8

Callbackjest po prostu środkiem do wykonania dowolnego kodu niestandardowego, który chcesz, gdy wywoływana jest jedna z metod makiety. Oto prosty przykład:

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

Niedawno natknąłem się na ciekawy przypadek użycia tego. Przypuśćmy, że spodziewasz się kilku wezwań do swojej sztuczki, ale są one wykonywane jednocześnie. Nie masz więc możliwości poznania kolejności, w jakiej będą dzwonione, ale chcesz wiedzieć, że połączenia, których oczekiwałeś, miały miejsce (niezależnie od kolejności). Możesz zrobić coś takiego:

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

A tak przy okazji, nie daj się zmylić wprowadzającym w błąd rozróżnieniem „przed Returns” i „po Returns”. Jest to jedynie techniczne rozróżnienie tego, czy Twój kod niestandardowy będzie działał po Returnsocenie, czy wcześniej. W oczach wywołującego obie będą działać przed zwróceniem wartości. Rzeczywiście, jeśli metoda voidpowraca, nie możesz nawet zadzwonić, Returnsa mimo to działa tak samo. Więcej informacji można znaleźć pod adresem https://stackoverflow.com/a/28727099/67824 .

Ohad Schneider
źródło
1

Oprócz innych dobrych odpowiedzi, użyłem go do wykonania logiki przed rzuceniem wyjątku. Na przykład musiałem przechowywać wszystkie obiekty, które zostały przekazane do metody w celu późniejszej weryfikacji, a ta metoda (w niektórych przypadkach testowych) musiała zgłosić wyjątek. Dzwoniąc .Throws(...)na Mock.Setup(...)Przesłania Callback()działania i nigdy nie nazywa. Jednak rzucając wyjątek w Callback, nadal możesz zrobić wszystkie dobre rzeczy, które ma do zaoferowania callback, i nadal zgłaszać wyjątek.

Frank Bryce
źródło