2013-06-11 9 views
23

Używam Moq i chcę utworzyć klasy budownicze, aby utworzyć moje makiety z ustawionymi rozsądnymi wartościami domyślnymi, które mogą w razie potrzeby nadpisać podczas konfiguracji testowej. Podejście, które podjąłem, wykorzystuje metody rozszerzania, w których przekazuję wartości parametrów wejściowych i oczekiwane wyniki. Czyniąc to, widzę inne zachowanie w tym, co wydaje mi się semantycznie równoważnym kodem: przekazywanie It.IsAny() bezpośrednio w konfiguracji vs przekazywanie wartości It.IsAny() pośrednio w konfiguracji. Przykład:Jaka jest różnica między przekazywaniem It.IsAny <int>() a wartością It.IsAny <int>() do metody konfiguracji

public interface IFoo 
{ 
    bool Bar(int value); 
    bool Bar2(int value); 
} 
public class Foo : IFoo 
{ 
    public bool Bar(int value) { return false; } 
    public bool Bar2(int value) { return false; } 
} 

var mock = new Mock<IFoo>(); 
mock.Setup(x => x.Bar(It.IsAny<int>())).Returns(true); 
Assert.IsTrue(mock.Object.Bar(123));     // Succeeds 

var myValue = It.IsAny<int>(); 
mock.Setup(x => x.Bar2(myValue)).Returns(true); 
Assert.IsTrue(mock.Object.Bar2(123));     // Fails 

Oba wywołania są równoważne (dla mnie), jednak wywołanie na Bar2 kończy się niepowodzeniem asercji. Dlaczego to?

Odpowiedz

33

It.IsAny zezwala tylko Moq na dopasowanie przyszłych wywołań wywołań metod, jeżeli są używane w konstrukcji Setup. Kiedy Setup nazywa się Moq, dodaje ono wywołanie metody do pamięci podręcznej już wywołanych wywołań metod. Zauważ, że argument do Setup w twoim przykładzie ma typ Expression<Func<IFoo, bool>>. Ponieważ przechodzisz w Expression, rzeczywiste wywołanie metody nie jest wywoływane, a Moq ma możliwość przechodzenia przez wyrażenie, aby dowiedzieć się, które parametry wywołania metody były jawne i które są argumentami It.IsAny. Używa tej możliwości do ustalenia, czy przyszłe wywołanie metody w środowisku wykonawczym jest zgodne z jednym z wywołań metod już skonfigurowanych.

Aby się tak, że sposób Bar może przyjąć argumentu It.IsAny<int>(), konieczne jest, aby It.IsAny<int>() zwrócić int (ponieważ jest to rodzaj parametru Bar). Ogólnie rzecz biorąc, typem zwrotu It.IsAny<T> musi być T. Należy wybrać dowolną wartość T. Najbardziej naturalnym wyborem jest default(T), który działa dla typów odniesienia i typów wartości. (Przeczytaj więcej o domyślnym słowie kluczowym here). W twoim przypadku jest to default(int), czyli 0.

Kiedy więc faktycznie ocenisz It.IsAny<int>(), natychmiast zwracana jest wartość 0. Jednak gdy użyjesz It.IsAny<int>() w Expression (jak w argumencie do metody Setup), wówczas struktura drzewa wywołania metody zostanie zachowana, a Moq może dopasować przyszłe wywołania metod do wywołania metody enkapsulowanego przez Expression.

Tak, chociaż nie można zachować It.IsAny<int>() jako zmienną w żaden znaczący sposób, można zachować całą Expression w zmiennej:

Expression<Func<IFoo, bool>> myExpr = x => x.Bar2(It.IsAny<int>()); 
mock.Setup(myExpr).Returns(true); 
Assert.IsTrue(mock.Object.Bar2(123)); 

Wreszcie, chciałbym przypomnieć, że Moq jest open source. Źródło jest dostępne here. Uważam, że warto mieć ten kod źródłowy, abym mógł klikać i eksplorować kod i testy jednostkowe.

+1

Doskonałe wyjaśnienie, Ben. Miałem podejrzenie, że problem leży w ocenie wyrażenia, jak opisujesz, a nie w wartości zwracanej przez It.IsAny (). Spojrzałem na odzwierciedlenie źródła w ILSpy i mogłem zobaczyć It.IsAny , ale nie mogłem dostać mojej głowy owiniętej wokół jak to działało w odniesieniu do problemu. Jeszcze raz dziękuję za wyjaśnienie. –

+0

Dzięki za oczyszczenie tych czarów dla mnie! Próbowałem powiedzieć "var anyFooParam = It.IsAny (); var anyBarParam = It.IsAny ();" aby sprawdzić, czy sprawiłoby, że moje testy byłyby bardziej czytelne, jak: .Setup (mock => mock.method (anyFooParam, anyBarParam)). Returns (something!) –

1

It.IsAny<int>() ma typ int i zwraca 0 wrócić, więc druga konfiguracja jest równoznaczne z:

mock.Setup(x => x.Bar2(0)).Returns(true); 

nie sprawdzić kod Min, ale jestem pewien, że kiedy oblicza wyrażenie w metodzie instalacji bierze pod uwagę, że parametr jest faktycznie It.IsAny vs normalny numer.

Lepiej jest, jeśli tworzysz konfiguracje bezpośrednio w swoich metodach pomocniczych i nie przekazujesz It.IsAny.