Jak mogę Moq metodę, która ma opcjonalny argument w podpisie bez jawnego określania go lub używania przeciążenia?

119

Biorąc pod uwagę następujący interfejs:

public interface IFoo
{
    bool Foo(string a, bool b = false);
}

Próba kpiny za pomocą Moq:

var mock = new Mock<IFoo>();
mock.Setup(mock => mock.Foo(It.IsAny<string>())).Returns(false);

daje następujący błąd w czasie kompilacji:

Drzewo wyrażeń nie może zawierać wywołania ani wywołania używającego opcjonalnych argumentów

Zauważyłem, że powyższy problem został podniesiony jako ulepszenie na liście problemów Moq i wydaje się, że jest on przypisany do wydania 4.5 (jeśli tak jest).

Moje pytanie brzmi: co powinienem zrobić, biorąc pod uwagę, że powyższe nie zostanie naprawione w najbliższym czasie? Czy moje opcje są tylko po to, aby albo jawnie ustawić domyślną wartość opcjonalnego parametru za każdym razem, gdy go mockuję (który rodzaj pokonuje punkt określania jednego w pierwszej kolejności) lub utworzyć przeciążenie bez bool (tak jakbym zrobił przed C # 4)?

Czy może ktoś znalazł bardziej sprytny sposób na rozwiązanie tego problemu?

Appulus
źródło
5
Czy rozsądne byłoby po prostu określenie It.IsAny <bool> () dla drugiego parametru?
Paul d'Aoust
Półtora roku później jest to nadal prawdą ...
Mukus
@Mukus, nie krępuj się zostawić PR, brachu.
IamDOM

Odpowiedzi:

91

Uważam, że teraz jedynym wyborem jest jawne uwzględnienie boolparametru w konfiguracji dla Foo.

Nie sądzę, żeby to przeczyło celowi określenia wartości domyślnej. Wartość domyślna to wygoda przy wywoływaniu kodu, ale myślę, że w testach powinieneś być wyraźny. Powiedzmy, że możesz pominąć określanie boolparametru. Co się stanie, jeśli w przyszłości ktoś zmieni domyślną wartość bna true? Doprowadzi to do niepowodzenia testów (i słusznie), ale będą one trudniejsze do naprawienia z powodu ukrytego założenia, jakim bjest false. Jawne określenie boolparametru ma jeszcze jedną zaletę: poprawia czytelność testów. Ktoś je przeglądając szybko zorientuje się, że istnieje jedna Foofunkcja, która przyjmuje dwa parametry. To przynajmniej moje 2 centy :)

Jeśli chodzi o określanie go za każdym razem, gdy go mockujesz, nie powielaj kodu: utwórz i / lub zainicjuj makietę w funkcji, aby mieć tylko jeden punkt zmiany. Jeśli naprawdę chcesz, możesz pokonać pozorne niedociągnięcia Moqa, powielając Fooparametry w tej funkcji inicjalizacyjnej:

public void InitFooFuncOnFooMock(Mock<IFoo> fooMock, string a, bool b = false)
{
    if(!b)
    {
        fooMock.Setup(mock => mock.Foo(a, b)).Returns(false);
    }
    else
    {
        ...
    }
}
Chris Mantle
źródło
1
Doskonała odpowiedź; Już poszedłem do przodu i wyraźnie to określiłem w moich kpinach, ale Twoja odpowiedź w bardzo jasny i logiczny sposób potwierdza, dlaczego powinienem to zrobić w ten sposób. Dzięki, @Chris.
Appulus
9
zmiana domyślnego parametru „powinno” przerywać testy. Brak niepowodzenia testów po zmianie wartości domyślnej może być oznaką złego testowania. Kod może używać wartości domyślnych, ale testy nie?
Pop Catalin
Minęło trochę czasu, ale wypróbowałem to podejście z Moq, próbując mockować interfejs (IDConnection w Dapper) i nadal otrzymuję ten sam błąd. Jakieś pomysły, dlaczego? Przykładowy wiersz: mockDB.Setup (x => x.Query <MyObject> (It.IsAny <string> (), It.IsAny <DynamicParameters> (), It.IsAny <IDbTransaction> (), false, 600)). Zwraca (new List <MyObject> ()); Ostatnie dwie wartości to opcjonalne parametry konfigurowanej przeze mnie metody.
Raelshark
4
Arrgggh! Straszny if (!x) {} else {}anty-wzór :)
nicodemus13
1
@ nicodemus13 Tak, ale starałem się, aby przykład kodu był jak najbardziej zbliżony do przykładu OP w pytaniu. Niekoniecznie bym tego popierał :)
Chris Mantle
8

Właśnie dzisiaj napotkałem ten problem, Moq nie obsługuje tego przypadku użycia. Wydaje się więc, że zastąpienie metody byłoby wystarczające w tym przypadku.

public interface IFoo
{
    bool Foo(string a);

    bool Foo(string a, bool b);
}

Teraz obie metody są dostępne i ten przykład zadziała:

var mock = new Mock<IFoo>();
mock.Setup(mock => mock.Foo(It.IsAny<string>())).Returns(false);
Gil Grossman
źródło
2

Korzystając z Moq w wersji 4.10.1 mogłem wykonać następujące czynności

Z interfejsem:

public interface IFoo
{
    bool Foo(string a, bool b = false);
}

I Mock

var mock = new Mock<IFoo>();
mock.Setup(mock => mock.Foo(It.IsAny<string>(), It.IsAny<bool>())).Returns(false);

Rozwiązuje wywołanie Foo z pierwszym parametrem w porządku

CF5
źródło