Чому я отримую виняток із повідомленням "Недійсне налаштування на невіртуальний (перезаписується в VB) член ..."?


176

У мене є одиничне тестування, де я маю знущатися над невіртуальним методом, який повертає тип 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);
    }
}

Отже, у мене є макетний об’єкт XmlCupboardAccessкласу, і я намагаюся встановити макет для цього методу у своєму тестовому випадку, як показано нижче

[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
}

Але цей рядок кидає виняток

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

Будь-яка пропозиція, як обійти цей виняток?


Від чого залежить тест XmlCupboardAccess?
Престон Гільо,

9
його просто .. вам потрібно це позначити virtual. Moq не може глузувати з конкретного типу, який він не може перекрити.
Саймон Уайтхед

Відповіді:


265

Moq не може знущатися над невіртуальними методами та герметичними класами. Під час запуску тесту з використанням макетного об'єкта MOQ фактично створює тип проксі-пам'яті, який успадковується від вашого "XmlCupboardAccess" і переосмислює поведінку, яку ви створили в методі "SetUp". І як ви знаєте на C #, ви можете перекрити щось, лише якщо воно позначено як віртуальне, що не так у Java. Java передбачає, що кожен нестатичний метод за замовчуванням є віртуальним.

Ще одна річ, яку я вважаю, вам слід розглянути, це запровадити інтерфейс для вашого "CupboardAccess" і замість цього почати глузувати інтерфейс. Це допоможе вам роз'єднати код і мати переваги в довгостроковій перспективі.

Нарешті, існують такі структури, як: TypeMock і JustMock, які працюють безпосередньо з ІЛ, і, отже, можуть глузувати з невіртуальних методів. Обидва, проте, є комерційними продуктами.


59
+1 про те, що вам слід лише глузувати з інтерфейсів. Це питання вирішило те, що я зіткнувся, тому що я випадково знущався над класом, а не основним інтерфейсом.
Пол Рафф

1
Це не тільки вирішує цю проблему, але добре застосовувати інтерфейси для всіх класів, які потребують тестування. Moq по суті змушує вас мати хорошу інверсію залежності, де деякі інші глузуючі рамки дозволяють вам обійти цей принцип.
Xipooo

Чи буде вважатися порушенням цього принципу, якщо у мене є фальшива реалізація, наприклад, FakePeopleRepository, мого інтерфейсу, наприклад, IPeopleRepository, і я знущаюся над фальшивою реалізацією? Я думаю, що IoC все ще зберігається, тому що в моїй тестовій установці я повинен передавати підроблений об'єкт моєму сервісному класу, який бере інтерфейс у його конструктор.
paz

1
@paz Вся суть використання MOQ полягає в тому, щоб уникнути підробленої реалізації. Тепер розглянемо, скільки варіантів підробленої реалізації вам знадобиться для перевірки граничних умов тощо. Теоретично, так, ви можете знущатися над фальшивою реалізацією. Але практично це звучить як кодовий запах.
Амол

Зауважте, що ця помилка насправді може статися при розширенні методів інтерфейсів, що може заплутати.
Ден Комора

34

Як допомогу будь-кому, хто мав ту саму проблему, що і я, я випадково помилково ввів тип реалізації замість інтерфейсу, наприклад

var mockFileBrowser = new Mock<FileBrowser>();

замість

var mockFileBrowser = new Mock<IFileBrowser>();

5

Будь ласка, подивіться, чому власність, яку я хочу висміяти, повинна бути віртуальною?

Можливо, вам доведеться написати інтерфейс обгортки або позначити властивість як віртуальне / абстрактне, оскільки Moq створює проксі-клас, який використовується для перехоплення викликів та повернення власних значень, які ви вводите у .Returns(x)виклик.


5

Замість того, щоб знущатися над конкретним класом, слід знущатися з інтерфейсу цього класу. Витяг інтерфейсу з класу XmlCupboardAccess

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

І замість цього

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

перейти

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

3

Ви також отримаєте цю помилку, якщо будете перевіряти, чи викликається метод розширення інтерфейсу.

Наприклад, якщо ви знущаєтесь:

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

Ви отримаєте той самий виняток, оскільки .ValidateAndThrow()це розширення IValidator<T>інтерфейсу.

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


-12

Код:

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

але дивіться виняток.


4
Чи можете ви надати пояснення свого рішення? Крім того, "див. Виняток ..." залишається висить. Чи можете ви розширити це?
амадан
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.