Zombar de objetos com Moq quando o construtor tem parâmetros

92

Tenho um objeto que estou tentando simular usando o moq. O construtor do objeto possui os parâmetros necessários:

public class CustomerSyncEngine {
    public CustomerSyncEngine(ILoggingProvider loggingProvider, 
                              ICrmProvider crmProvider, 
                              ICacheProvider cacheProvider) { ... }
}

Agora estou tentando criar a simulação para este objeto usando a sintaxe "setup" v3 ou v4 "Mock.Of" do moq, mas não consigo descobrir isso ... tudo o que estou tentando não está validando. Aqui está o que eu tenho até agora, mas a última linha está me dando um objeto real, não a simulação. Estou fazendo isso porque tenho métodos no CustomerSyncEngine que desejo verificar se estão sendo chamados ...

// setup
var mockCrm = Mock.Of<ICrmProvider>(x => x.GetPickLists() == crmPickLists);
var mockCache = Mock.Of<ICacheProvider>(x => x.GetPickLists() == cachePickLists);
var mockLogger = Mock.Of<ILoggingProvider>();

// need to mock the following, not create a real class like this...
var syncEngine = new CustomerSyncEngine(mockLogger, mockCrm, mockCache);
Andrew Connell
fonte
Você pode fornecer um método de amostra que deseja verificar sendo chamado?
Ciaran
4
Portanto, se eu tiver dependências em Classes em vez de Interfaces, preciso simular até mesmo as dependências delas, isso diminui recursivamente. No final, sou forçado a usar algumas interfaces para manter meu código testável, mesmo se eu não precisar das interfaces em meu código. Eu acho que muitas interfaces são um cheiro
pior do

Respostas:

34

A última linha está fornecendo uma instância real porque você está usando a nova palavra-chave, não zombando de CustomerSyncEngine.

Você deveria usar Mock.Of<CustomerSyncEngine>()

O único problema com os tipos de simulação de concreto é que o Moq precisaria de um construtor público padrão (sem parâmetros) OU você precisa criar o Moq com a especificação do construtor arg. http://www.mockobjects.com/2007/04/test-smell-mocking-concrete-classes.html

A melhor coisa a fazer seria clicar com o botão direito na sua classe e escolher Extrair interface.

Raghu
fonte
3
Em relação ao problema, uma alternativa é usar um contêiner de AutoMocking. Meu favorito é Machine.Fakes em conjunto com Machine.Specifications, usando um contêiner de armazenamento automático torna mais fácil testar áreas de superfície menores. Suponha que Andrew precise testar um método CustomerSyncEngineque só use ICrmProvidercom implementações de mocking tradicionais para todas as 3 interfaces, enquanto um contêiner de autmocking permitiria que você fornecesse apenas um.
Chris Marisic
73

Altere a última linha para

var syncEngine = new Mock<CustomerSyncEngine>(mockLogger, mockCrm, mockCache).Object;

e deve funcionar

Suhas
fonte
3
Não tem certeza de como este comentário se aplica à minha resposta?
Suhas
2
Porque isso causaria um erro de compilação, pois o mockLogger e outros lançariam uma exceção de que eles não têm uma propriedade Object
Justin Pihony
2
Como o OP está usando Mock.Of <T> () para criar simulações dos tipos de logger, crm e cache, o objeto retornado é retornado como T, não como Mock <T>. Portanto, mockLogger.Object etc. não é necessário ao fornecê-los ao Mock do CustomerSyncEngine e, como @JustinPihony mencionou, deve mostrar um erro de tempo de design.
Josh Gust
1
@suhas não deveria sernew Mock<CustomerSyncEngine>(new object[]{mockLogger, mockCrm, mockCache}).Object;
GiriB
@GiriB não é necessário, mas possível, pois o mock é definido com Params. Mock público (objeto params [] args)
Jiří Herník