Moq: jak dostać się do parametru przekazanego do metody usługi mockowanej

169

Wyobraź sobie tę klasę

public class Foo {

    private Handler _h;

    public Foo(Handler h)
    {
        _h = h;
    }

    public void Bar(int i)
    {
        _h.AsyncHandle(CalcOn(i));
    }

    private SomeResponse CalcOn(int i)
    {
        ...;
    }
}

Mo (q) cking Handler w teście Foo, w jaki sposób mógłbym sprawdzić, co Bar()przeszło _h.AsyncHandle?

Jan
źródło
Czy chodziło Ci o „AsyncHandle” (extra „n”)? Czy możesz opublikować kod dla programu obsługi lub określić w pełni kwalifikowaną nazwę typu, jeśli jest to typ standardowy?
TrueWill,
Czy możesz pokazać swój test szkieletu, aby pokazać, o czym myślisz? Chociaż doceniam to, że z Twojej strony jest to oczywiste, z naszej strony wygląda na to, że ktoś nie poświęcił czasu na udzielenie odpowiedzi na pytanie bez długich spekulacyjnych odpowiedzi.
Ruben Bartelink,
1
Nie ma ani Foo, ani Bar (), ani niczego podobnego. To tylko kod demonstracyjny, który pokazuje sytuację, w której się znajduję, bez odwracania uwagi od specyfiki aplikacji. I dostałem tylko odpowiedź, którą miałem nadzieję otrzymać.
stycznia

Odpowiedzi:

283

Możesz użyć metody Mock.Callback:

var mock = new Mock<Handler>();
SomeResponse result = null;
mock.Setup(h => h.AnsyncHandle(It.IsAny<SomeResponse>()))
    .Callback<SomeResponse>(r => result = r);

// do your test
new Foo(mock.Object).Bar(22);
Assert.NotNull(result);

Jeśli chcesz sprawdzić tylko coś prostego w przekazanym argumencie, możesz to również zrobić bezpośrednio:

mock.Setup(h => h.AnsyncHandle(It.Is<SomeResponse>(response => response != null)));
Gamlor
źródło
36
Na marginesie, jeśli masz wiele argumentów do swojej funkcji, musisz określić wszystkie typy w ogólnej Callback<>()metodzie Moq. Na przykład, jeśli metoda miałaby definicję Handler.AnsyncHandle(string, SomeResponse), potrzebowałbyś /* ... */.Callback<string, SomeResponse>(r => result = r);. Nie znalazłem tego wyraźnie w wielu miejscach, więc pomyślałem, że dodam to tutaj.
Frank Bryce
12
Chciałem tylko poprawić @Frank na wypadek, gdybyś nie widział odpowiedzi @JavaJudt. Prawidłowy sposób na uzyskanie dwóch argumentów to:/* ... */.Callback<string, SomeResponse>((s1, s2) => { str1 = s1; result = s2});
renatogbp
2
Możesz również osiągnąć to samo, po prostu deklarując typy argumentów w wyrażeniu lambda, na przykład:.Callback((string s1, SomeResponse s2) => /* stuff */ )
jb637
1
Czy możesz zaktualizować swoją odpowiedź, aby zawierała odniesienie do wbudowanego Capture.Inpomocnika?
cao
29

Odpowiedź Gamlora zadziałała dla mnie, ale pomyślałem, że rozszerzę komentarz Johna Carpentera, ponieważ szukałem rozwiązania obejmującego więcej niż jeden parametr. Pomyślałem, że inni ludzie, którzy natkną się na tę stronę, mogą być w podobnej sytuacji. Znalazłem te informacje w dokumentacji Moq .

Posłużę się przykładem Gamlora, ale załóżmy, że metoda AsyncHandle przyjmuje dwa argumenty: stringai SomeResponseobiekt.

var mock = new Mock<Handler>();
string stringResult = string.Empty;
SomeResponse someResponse = null;
mock.Setup(h => h.AsyncHandle(It.IsAny<string>(), It.IsAny<SomeResponse>()))
    .Callback<string, SomeResponse>((s, r) => 
    {
        stringResult = s;
        someResponse = r;
    });

// do your test
new Foo(mock.Object).Bar(22);
Assert.AreEqual("expected string", stringResult);
Assert.IsNotNull(someResponse);

Zasadniczo wystarczy dodać inny It.IsAny<>()z odpowiednim typem, dodać inny typ do Callbackmetody i odpowiednio zmienić wyrażenie lambda.

JavaJudt
źródło
22

Metoda Callback z pewnością zadziała, ale jeśli robisz to na metodzie z wieloma parametrami, może to być nieco rozwlekłe. Tutaj jest coś, czego użyłem do usunięcia części gotówki.

var mock = new Mock<Handler>();

// do your test   
new Foo(mock.Object).Bar(22);

var arg = new ArgumentCaptor<SomeResponse>();
mock.Verify(h => h.AsyncHandle(arg.Capture()));
Assert.NotNull(arg.Value);

Oto źródło ArgumentCaptor:

public class ArgumentCaptor<T>
{
    public T Capture()
    {
        return It.Is<T>(t => SaveValue(t));
    }

    private bool SaveValue(T t)
    {
        Value = t;
        return true;
    }

    public T Value { get; private set; }
}
Andrew Radford
źródło
21

Odpowiedź Gamlora działa, ale inny sposób (i który uważam za bardziej wyrazisty w teście) to ...

var mock = new Mock<Handler>();
var desiredParam = 47; // this is what you want to be passed to AsyncHandle
new Foo(mock.Object).Bar(22);
mock.Verify(h => h.AsyncHandle(desiredParam), Times.Once());

Weryfikacja jest bardzo potężna i warto poświęcić trochę czasu na przyzwyczajenie się.

Pete Martin
źródło
15
Takie podejście jest dobre, jeśli chcesz tylko sprawdzić, czy metoda została wywołana ze znanym parametrem. W przypadku, gdy parametr nie został jeszcze utworzony w momencie pisania testu (np. Jednostka, o której mowa, tworzy parametr wewnętrznie), wówczas funkcja Callback umożliwia wychwycenie i zbadanie tego, podczas gdy Twoje podejście nie.
Michael,
1
Muszę przechowywać przekazaną wartość, ponieważ muszę sprawdzić, czy przeszedł cały zestaw obiektów.
MrFox
Czasami parametr jest jednostką i potrzebujesz osobnych potwierdzeń dla każdego pola w jednostce. Najlepszym sposobem na to jest przechwycenie parametru, zamiast używania weryfikacji i dopasowania.
Kevin Wong
12

Alternatywą jest również użycie Capture.Infunkcji moq. Jest to moqfunkcja OOTB, która umożliwia przechwytywanie argumentów w kolekcji.

//Arrange
var args = new List<SomeResponse>();
mock.Setup(h => h.AnsyncHandle(Capture.In(args)));

//Act
new Foo(mock.Object).Bar(22);

//Assert
//... assert args.Single() or args.First()
Jasio
źródło
1
Świetna odpowiedź! Nie wiedziałem o klasach Moq.Capture i Moq.CaptureMatch, dopóki nie zobaczyłem tej odpowiedzi. Przechwytywanie jest lepszą alternatywą dla CallbackIMO. Ponieważ Capture jest używany bezpośrednio na liście parametrów, jest on znacznie mniej podatny na problemy podczas refaktoryzacji listy parametrów metody, a zatem sprawia, że ​​testy są mniej kruche. W przypadku wywołania zwrotnego musisz utrzymywać parametry przekazane w konfiguracji zsynchronizowane z parametrami typu używanymi do wywołania zwrotnego i na pewno sprawiało mi to w przeszłości problemy.
Justin Holzer
7

Możesz użyć It.Is<TValue>()dopasowania.

var mock = new Mock<Handler>();
new Foo(mock.Object).Bar(22);
mock.Verify(h => h.AsyncHandle(It.Is<SomeResponse>(r => r != null )));
Ed Yablonsky
źródło
2

Działa to również:

Mock<InterfaceThing> mockedObject = new Mock<InterfaceThing>();
var objectParameter = mockedObject.Invocations[1].Arguments[0] as ObjectParameter;
Jeff Smith
źródło
2

Wiele dobrych odpowiedzi tutaj! Idź z gotowym zestawem funkcji Moq, aż będziesz musiał dokonać asercji dotyczących kilku parametrów klas przekazanych do twoich zależności. Jeśli jednak znajdziesz się w takiej sytuacji, funkcja Moq Verify with It. Is matchers nie radzi sobie dobrze z izolowaniem błędu testu, a sposób Returns / Callback przechwytywania argumentów dodaje niepotrzebne wiersze kodu do twojego testu (i długie testy są dla mnie nie do przyjęcia).

Oto sedno: https://gist.github.com/Jacob-McKay/8b8d41ebb9565f5fca23654fd944ac6b z rozszerzeniem Moq (4.12), które napisałem, które daje bardziej deklaratywny sposób tworzenia twierdzeń o argumentach przekazywanych do kpiny, bez wspomnianych powyżej wad. Oto, jak teraz wygląda sekcja Weryfikuj:

        mockDependency
            .CheckMethodWasCalledOnce(nameof(IExampleDependency.PersistThings))
            .WithArg<InThing2>(inThing2 =>
            {
                Assert.Equal("Input Data with Important additional data", inThing2.Prop1);
                Assert.Equal("I need a trim", inThing2.Prop2);
            })
            .AndArg<InThing3>(inThing3 =>
            {
                Assert.Equal("Important Default Value", inThing3.Prop1);
                Assert.Equal("I NEED TO BE UPPER CASED", inThing3.Prop2);
            });

Byłbym podekscytowany, gdyby Moq dostarczył funkcję, która osiągnęła to samo, będąc deklaratywną i zapewniając izolację awarii. Skrzyżowane palce!

Jacob McKay
źródło
1
Lubię to. Verify of Moq konkuruje z Assert of xUnit o uprawnienia do wykonywania potwierdzeń. To nie wydaje się dobre w części konfiguracji Moq. Konfiguracja funkcji It.Is również powinna obsługiwać inne niż wyrażenia.
Thomas
„Moq konkuruje z Assertem xUnit o autorytet w wykonywaniu assertów” - dobrze powiedział @Thomas. Dodam, że przegrałby konkurencję. Moq jest dobry w informowaniu o tym, czy było pasujące wywołanie, ale konkretne potwierdzenia dają znacznie lepsze informacje. Główną wadą mojego podejścia jest utrata bezpieczeństwa typu i kolejności sprawdzania parametrów. Od jakiegoś czasu szukałem ulepszeń w stosunku do tego, mam nadzieję, że jest tam ninja C #, który może razem zhakować przykład! W przeciwnym razie, jeśli znajdę sposób, zaktualizuję to.
Jacob McKay