Dlaczego otrzymuję wyjątek z komunikatem „Nieprawidłowa konfiguracja na elemencie niewirtualnym (nadpisywalnym w VB)…”?

176

Mam test jednostkowy, w którym muszę mockować metodę niewirtualną, która zwraca typ bool

public class XmlCupboardAccess
{
    public bool IsDataEntityInXmlCupboard(string dataId,
                                          out string nameInCupboard,
                                          out string refTypeInCupboard,
                                          string nameTemplate = null)
    {
        return IsDataEntityInXmlCupboard(_theDb, dataId, out nameInCupboard, out refTypeInCupboard, nameTemplate);
    }
}

Mam więc pozorowany obiekt XmlCupboardAccessklasy i próbuję skonfigurować makietę dla tej metody w moim przypadku testowym, jak pokazano poniżej

[TestMethod]
Public void Test()
{
    private string temp1;
    private string temp2;
    private Mock<XmlCupboardAccess> _xmlCupboardAccess = new Mock<XmlCupboardAccess>();
    _xmlCupboardAccess.Setup(x => x.IsDataEntityInXmlCupboard(It.IsAny<string>(), out temp1, out temp2, It.IsAny<string>())).Returns(false); 
    //exception is thrown by this line of code
}

Ale ta linia rzuca wyjątek

Invalid setup on a non-virtual (overridable in VB) member: 
x => x.IsDataEntityInXmlCupboard(It.IsAny<String>(), .temp1, .temp2, 
It.IsAny<String>())

Jakieś sugestie, jak obejść ten wyjątek?

Rahul Lodha
źródło
Od czego zależy twój test XmlCupboardAccess?
Preston Guillot
9
jego proste ... musisz to zaznaczyć virtual. Moq nie może kpić z konkretnego typu, którego nie może zastąpić.
Simon Whitehead,

Odpowiedzi:

265

Moq nie może mockować niewirtualnych metod i klas zapieczętowanych. Podczas wykonywania testu przy użyciu pozorowanego obiektu, MOQ faktycznie tworzy w pamięci typ proxy, który dziedziczy po twoim „XmlCupboardAccess” i przesłania zachowania, które ustawiłeś w metodzie „SetUp”. Jak wiesz w C #, możesz przesłonić coś tylko wtedy, gdy jest oznaczone jako wirtualne, co nie ma miejsca w przypadku Javy. Java zakłada, że ​​każda metoda niestatyczna jest domyślnie wirtualna.

Inną rzeczą, którą uważam, że powinieneś rozważyć, jest wprowadzenie interfejsu dla twojego "CupboardAccess" i zamiast tego zacząć kpić z interfejsu. Pomogłoby to oddzielić kod i przyniosłoby korzyści w dłuższej perspektywie.

Wreszcie istnieją frameworki, takie jak: TypeMock i JustMock, które działają bezpośrednio z IL i dlatego mogą mockować metody niewirtualne. Oba są jednak produktami komercyjnymi.

Amol
źródło
59
+1 na fakt, że powinieneś tylko kpić z interfejsów. To pytanie rozwiązało problem, na który natknąłem się, ponieważ przypadkowo wyszydziłem klasę, a nie podstawowy interfejs.
Paul Raff
1
Nie tylko rozwiązuje to problem, ale dobrą praktyką jest używanie interfejsów dla wszystkich klas, które wymagają testowania. Moq zasadniczo zmusza cię do dobrego odwrócenia zależności, gdzie niektóre z innych mockujących frameworków pozwalają ci obejść tę zasadę.
Xipooo
Czy byłoby to uważane za naruszenie tej zasady, jeśli mam fałszywą implementację, np. FakePeopleRepository, mojego interfejsu, np. IPeopleRepository, i kpię z fałszywej implementacji? Myślę, że IoC jest nadal zachowane, ponieważ w mojej konfiguracji testowej muszę przekazać fałszywy obiekt do mojej klasy usługi, która pobiera interfejs w swoim konstruktorze.
paz
1
@paz Cały sens korzystania z MOQ polega na uniknięciu fałszywej implementacji. Zastanów się teraz, ile wariantów fałszywej implementacji trzeba byłoby sprawdzić, aby sprawdzić warunki brzegowe itp. Teoretycznie tak, można by kpić z fałszywej implementacji. Ale praktycznie brzmi to jak zapach kodu.
Amol
Zwróć uwagę, że ten błąd może faktycznie wystąpić w przypadku metod rozszerzających na interfejsach, co może być mylące.
Dan Pantry,
34

Jako pomoc każdemu, kto miał ten sam problem co ja, przypadkowo błędnie wpisałem typ implementacji zamiast interfejsu np

var mockFileBrowser = new Mock<FileBrowser>();

zamiast

var mockFileBrowser = new Mock<IFileBrowser>();
Ralt
źródło
5

Zamiast kpić z konkretnej klasy, powinieneś kpić z tego interfejsu klasy. Wyodrębnij interfejs z klasy XmlCupboardAccess

public interface IXmlCupboardAccess
{
    bool IsDataEntityInXmlCupboard(string dataId, out string nameInCupboard, out string refTypeInCupboard, string nameTemplate = null);
}

A zamiast tego

private Mock<XmlCupboardAccess> _xmlCupboardAccess = new Mock<XmlCupboardAccess>();

zmień na

private Mock<IXmlCupboardAccess> _xmlCupboardAccess = new Mock<IXmlCupboardAccess>();
Sashus
źródło
3

Otrzymasz ten błąd również, jeśli sprawdzasz, czy wywoływana jest metoda rozszerzenia interfejsu.

Na przykład, jeśli kpisz:

var mockValidator = new Mock<IValidator<Foo>>();
mockValidator
  .Verify(validator => validator.ValidateAndThrow(foo, null));

Otrzymasz ten sam wyjątek, ponieważ .ValidateAndThrow()jest to rozszerzenie IValidator<T>interfejsu.

public static void ValidateAndThrow<T>(this IValidator<T> validator, T instance, string ruleSet = null)...

Scotty.NET
źródło
-12

Kod:

private static void RegisterServices(IKernel kernel)
{
    Mock<IProductRepository> mock=new Mock<IProductRepository>();
    mock.Setup(x => x.Products).Returns(new List<Product>
    {
        new Product {Name = "Football", Price = 23},
        new Product {Name = "Surf board", Price = 179},
        new Product {Name = "Running shose", Price = 95}
    });

    kernel.Bind<IProductRepository>().ToConstant(mock.Object);
}        

ale zobacz wyjątek.

Borat
źródło
4
Czy mógłbyś wyjaśnić swoje rozwiązanie? Ponadto komunikat „zobacz wyjątek ...” pozostaje zawieszony. Czy możesz to rozwinąć?
amadan