Por que estou recebendo uma exceção com a mensagem "Configuração inválida em um membro não virtual (substituível no VB) ..."?

176

Eu tenho um teste de unidade em que eu tenho que zombar de um método não virtual que retorna um tipo de 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);
    }
}

Portanto, eu tenho um objeto de XmlCupboardAccessclasse falso e estou tentando configurar o mock para esse método no meu caso de teste, como mostrado abaixo

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

Mas esta linha lança exceção

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

Alguma sugestão de como contornar essa exceção?

Rahul Lodha
fonte
Em que depende o seu teste XmlCupboardAccess?
Preston Guillot
9
é simples .. você precisa marcá-lo virtual. O Moq não pode simular um tipo concreto que não pode substituir.
Simon Whitehead

Respostas:

265

O Moq não pode simular métodos não virtuais e classes seladas. Durante a execução de um teste usando objeto simulado, o MOQ na verdade cria um tipo de proxy na memória que herda do seu "XmlCupboardAccess" e substitui os comportamentos que você configurou no método "SetUp". E como você sabe em C #, você pode substituir algo apenas se estiver marcado como virtual, o que não é o caso do Java. Java pressupõe que todo método não estático seja virtual por padrão.

Outra coisa que acredito que você deve considerar é a introdução de uma interface para o seu "CupboardAccess" e, em vez disso, comece a zombar da interface. Isso o ajudaria a desacoplar seu código e obter benefícios a longo prazo.

Por fim, existem estruturas como: TypeMock e JustMock, que funcionam diretamente com a IL e, portanto, podem simular métodos não virtuais. Ambos, no entanto, são produtos comerciais.

Amol
fonte
59
+1 no fato de que você deve simular apenas interfaces. Essa pergunta resolveu o que eu estava encontrando, porque acidentalmente zombei da classe e não da interface subjacente.
Paul Raff
1
Isso não apenas resolve o problema, mas é uma boa prática usar interfaces para todas as suas classes que precisam de teste. O Moq está essencialmente forçando você a ter uma boa inversão de dependência, onde, como algumas outras estruturas de zombaria, você pode contornar esse princípio.
Xipooo
Seria considerado uma violação deste princípio se eu tivesse uma implementação falsa, por exemplo, FakePeopleRepository, da minha interface, por exemplo, IPeopleRepository, e estou zombando da implementação falsa? Eu acho que a IoC ainda está preservada porque, na minha configuração de teste, tenho que passar o objeto falso para minha classe de serviço, que leva a interface em seu construtor.
Paz
1
@paz O objetivo principal de usar o MOQ é evitar a implementação falsa. Agora considere quantas variantes da implementação falsa você precisaria para verificar as condições de contorno etc. Em teoria, sim, você poderia zombar de uma implementação falsa. Mas praticamente soa como um cheiro de código.
Amol
Observe que esse erro pode realmente ocorrer com métodos de extensão em interfaces, o que pode ser confuso.
Dan Pantry
34

Como ajuda a qualquer pessoa que tenha o mesmo problema que eu, acidentalmente digitei incorretamente o tipo de implementação em vez da interface, por exemplo

var mockFileBrowser = new Mock<FileBrowser>();

ao invés de

var mockFileBrowser = new Mock<IFileBrowser>();
Ralt
fonte
5

Consulte Por que a propriedade que eu quero zombar precisa ser virtual?

Pode ser necessário escrever uma interface do wrapper ou marcar a propriedade como virtual / abstrata, pois o Moq cria uma classe de proxy usada para interceptar chamadas e retornar seus valores personalizados que você coloca na .Returns(x)chamada.

Bryida
fonte
5

Em vez de zombar da classe concreta, você deve zombar dessa interface de classe. Extrair interface da classe XmlCupboardAccess

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

E em vez de

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

mudar para

private Mock<IXmlCupboardAccess> _xmlCupboardAccess = new Mock<IXmlCupboardAccess>();
Sashus
fonte
3

Você também receberá esse erro se estiver verificando se um método de extensão de uma interface é chamado.

Por exemplo, se você estiver zombando:

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

Você receberá a mesma exceção, porque .ValidateAndThrow()é uma extensão na IValidator<T>interface.

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

Scotty.NET
fonte
-12

Código:

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

mas veja a exceção.

Borat
fonte
4
Você poderia fornecer uma explicação para sua solução? Além disso, "veja a exceção ..." fica suspenso. Você pode expandir isso?
Amadan