Почему я получаю исключение с сообщением "Неверная настройка для не виртуального (переопределяемого в VB) члена..."?

У меня есть модульный тест, где я должен смоделировать не виртуальный метод, который возвращает тип 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>())

Любое предложение, как обойти это исключение?

7 ответов

Решение

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

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

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

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

var mockFileBrowser = new Mock<FileBrowser>();

вместо

var mockFileBrowser = new Mock<IFileBrowser>();

Вместо того, чтобы издеваться над конкретным классом, вы должны высмеивать интерфейс этого класса. Извлечь интерфейс из класса 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>();

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

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

Вы также получите эту ошибку, если убедитесь, что вызывается метод расширения интерфейса.

Например, если вы издеваетесь:

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

В моем случае я использовал версию Moq ниже 4.16 и использовал .Result синтаксис для имитации асинхронного метода, который поддерживается только начиная с Moq 4.16

На макетной версии ниже 4.16 следующие результаты в Invalid setup on a non-virtual member ... даже при использовании интерфейса.

      mock.Setup(foo => foo.DoSomethingAsync().Result).Returns(true);

В версии Moq ниже 4.16 используйте следующие

      mock.Setup(foo => foo.DoSomethingAsync()).ReturnsAsync(true);

Для получения дополнительной информации см. Async Methods Moq Wiki на Github.

Код:

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

но вижу исключение.

Другие вопросы по тегам