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?


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.


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>();


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>();

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)...


-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.


4
Czy mógłbyś wyjaśnić swoje rozwiązanie? Ponadto komunikat „zobacz wyjątek ...” pozostaje zawieszony. Czy możesz to rozwinąć?
— amadan
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.