Почему я получаю исключение с сообщением «Неверная настройка для не виртуального (переопределяемого в 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. Мок не может издеваться над конкретным типом, который он не может переопределить.
Саймон Уайтхед

Ответы:

265

Moq не может высмеивать не виртуальные методы и запечатанные классы. Во время выполнения теста с использованием фиктивного объекта MOQ фактически создает прокси-тип в памяти, который наследуется от вашего «XmlCupboardAccess» и переопределяет поведение, настроенное вами в методе «SetUp». И, как вы знаете в C #, вы можете переопределить что-то, только если оно помечено как виртуальное, что не так с Java. Java предполагает, что каждый нестатический метод является виртуальным по умолчанию.

Еще одна вещь, на которую, я полагаю, вам стоит обратить внимание, это ввести интерфейс для вашего «CupboardAccess» и вместо этого начать дразнить интерфейс. Это поможет вам отделить ваш код и получит преимущества в долгосрочной перспективе.

Наконец, существуют такие платформы, как: TypeMock и JustMock, которые работают напрямую с IL и, следовательно, могут имитировать не виртуальные методы. Оба, однако, являются коммерческими продуктами.

Amol
источник
59
+1 на то, что надо только издеваться над интерфейсами. Этот вопрос решил то, с чем я столкнулся, потому что я случайно высмеял класс, а не базовый интерфейс.
Пол Рафф
1
Это не только решает проблему, но и хорошо использовать интерфейсы для всех ваших классов, которые нуждаются в тестировании. Moq по сути вынуждает вас иметь хорошую инверсию зависимостей, в то время как некоторые другие насмешливые фреймворки позволяют вам обойти этот принцип.
Xipooo
Будет ли считаться нарушением этого принципа, если у меня есть поддельная реализация, например, FakePeopleRepository, моего интерфейса, например, IPeopleRepository, и я высмеиваю поддельную реализацию? Я думаю, что IoC все еще сохраняется, потому что в моей тестовой настройке я должен передать поддельный объект моему классу обслуживания, который принимает интерфейс в своем конструкторе.
Паз
1
@paz Смысл использования MOQ в том, чтобы избежать ложной реализации. Теперь рассмотрим, сколько вариантов поддельной реализации вам понадобится, чтобы проверить граничные условия и т. Д. Теоретически, да, вы можете смоделировать ложную реализацию. Но практически это звучит как запах кода.
Амол
Обратите внимание, что эта ошибка на самом деле может возникнуть с методами расширения на интерфейсах, что может привести к путанице.
Дэн Кладовая
34

В качестве помощи любому, у кого была такая же проблема, как у меня, я случайно набрал тип реализации вместо интерфейса, например

var mockFileBrowser = new Mock<FileBrowser>();

вместо того

var mockFileBrowser = new Mock<IFileBrowser>();
Ralt
источник
5

Посмотри пожалуйста почему собственность, которую я хочу высмеять, должна быть виртуальной?

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

Bryida
источник
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>();
Sashus
источник
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)...

Scotty.NET
источник
-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
Не могли бы вы дать объяснение вашего решения? Кроме того, "видеть исключение ..." остается висеть. Вы можете расширить это?
Amadan