Como zombar de método com objeto codificado?

11

Estou trabalhando em um aplicativo que possui várias camadas. Camada de acesso a dados para recuperar e salvar dados da fonte de dados, lógica de negócios para manipular dados, interface do usuário para mostrar os dados na tela.

Também estou fazendo testes de unidade da camada de lógica de negócios. O único requisito é testar o fluxo da lógica da camada de negócios. Então, eu uso a estrutura Moq para simular a camada de acesso a dados e testar a camada de lógica de negócios com a unidade MS.

Estou usando a programação da interface para fazer o design desacoplar o máximo possível, para que o teste de unidade possa ser realizado. A camada de negócios chama a camada de acesso a dados através da interface.

Estou enfrentando um problema ao tentar testar um dos métodos de lógica de negócios. Esse método funciona e cria um objeto e o passa para a camada de acesso a dados. Quando estou tentando zombar desse método da camada de acesso a dados, ele não pode ser zombado com êxito.

Aqui estou tentando criar um código de demonstração para mostrar meu problema.

Modelo:

public class Employee
{
    public string Name { get; set; }
}

Camada de acesso a dados:

public interface IDal
{
    string GetMessage(Employee emp);
}

public class Dal : IDal
{
    public string GetMessage(Employee emp)
    {
        // Doing some data source access work...

        return string.Format("Hello {0}", emp.Name);
    }
}

Camada de lógica de negócios:

public interface IBll
{
    string GetMessage();
}

public class Bll : IBll
{
    private readonly IDal _dal;

    public Bll(IDal dal)
    {
        _dal = dal;
    }

    public string GetMessage()
    {
        // Object creating inside business logic method.
        Employee emp = new Employee(); 

        string msg = _dal.GetMessage(emp);
        return msg;
    }
}

Teste de unidade:

[TestMethod]
    public void Is_GetMessage_Return_Proper_Result()
    {
        // Arrange.
        Employee emp = new Employee; // New object.

        Mock<IDal> mockDal = new Mock<IDal>();
        mockDal.Setup(d => d.GetMessage(emp)).Returns("Hello " + emp.Name);

        IBll bll = new Bll(mockDal.Object);

        // Act.

        // This will create another employee object inside the 
        // business logic method, which is different from the 
        // object which I have sent at the time of mocking.
        string msg = bll.GetMessage(); 

        // Assert.
        Assert.AreEqual("Hello arnab", msg);
    }

No caso de teste de unidade, no momento da zombaria, estou enviando um objeto Employee, mas ao invocar o método da lógica de negócios, ele cria um objeto Employee diferente dentro do método. É por isso que não posso zombar do objeto.

Nesse caso, como projetar para que eu possa resolver o problema?

DesenvolvedorArnab
fonte
Normalmente, o truque é envolver o objeto em uma interface e fazer com que todos os consumidores usem essa interface; você apenas zomba da interface; alternativamente, pode tornar o método virtual e, em seguida, o moq pode zombar do método sem a interface. No entanto, não tenho certeza sobre rhinomocks ou outros.
Jimmy Hoffa

Respostas:

12

Em vez de criar um Employeeobjeto diretamente usando new, sua classe Bllpoderia usar uma EmployeeFactoryclasse para isso, com um método createInstanceque é injetado pelo construtor:

 class EmployeeFactory : IEmployeeFactory
 {
       public Employee createInstance(){return new Employee();}
 }

O construtor deve levar o objeto de fábrica através de uma interface IEmployeeFactory, para que você possa substituir facilmente a fábrica "real" por uma fábrica simulada.

public class Bll : IBll
{
    private readonly IDal _dal;
    private readonly IEmployeeFactory _employeeFactory;

    public Bll(IDal dal, IEmployeeFactory employeeFactory)
    {
        _dal = dal;
        _employeeFactory=employeeFactory;
    }

    public string GetMessage()
    {
        // Object creating inside business logic method
        // *** using a factory ***
        Employee emp = _employeeFactory.createObject(); 
        // ...
    }
    //...
}

A fábrica simulada pode fornecer ao teste qualquer tipo de Employeeobjeto que você precise (por exemplo, createInstancesempre pode retornar o mesmo objeto):

 class MockEmployeeFactory : IEmployeeFactory
 {
       private Employee _emp;

       public MockEmployeeFactory()
       {
          _emp = new Employee();
          // add any kind of special initializing here for testing purposes
       }

       public Employee createInstance()
       {
          // just for testing, return always the same object
          return _emp;
       }
 }

Agora, usar esse mock no seu teste deve fazer o truque.

Doc Brown
fonte
Você pode me dar um exemplo de código, para que eu possa visualizar sua teoria?
DeveloperArnab
@DeveloperArnab: veja minha edição.
Doc Brown
Muito útil ...
DeveloperArnab
4

Eu trataria isso como uma única unidade para testar.

Desde que você controle todas as entradas das quais o Employeeobjeto é criado, o fato de ele ser criado no objeto testado não deve importar. Você só precisa de um método mock para retornar o resultado esperado se o conteúdo do argumento corresponder à expectativa.

Obviamente, isso significa que você precisa fornecer lógica personalizada para o método simulado. A lógica avançada geralmente não pode ser testada apenas com o tipo de simulação "for x return y".

De fato, você não deve fazer com que ele retorne objetos diferentes nos testes do que na produção, porque, se o fizesse, não estaria testando o código que deveria criá-lo. Mas esse código é parte integrante do código de produção e, portanto, também deve ser coberto pelo caso de teste.

Jan Hudec
fonte
Sim, não me preocupo com as entradas da camada de acesso a dados. Quero apenas zombar desse objeto e retornar dados codificados para que eu possa testar a lógica de negócios. Mas o problema é por causa de dois objetos Employee diferentes, não posso zombar do método da camada de acesso a dados.
DeveloperArnab
@DeveloperArnab: Os objetos serão diferentes, mas terão conteúdo conhecido. Então, tudo o que você precisa fazer é fazer a simulação fazer uma comparação personalizada em vez da identidade do objeto.
Jan Hudec
@DeveloperArnab: Se você injetar um Employeeobjeto diferente nos testes, não testará o código que normalmente o cria. Então você não deve mudar isso.
precisa
0

É uma falha de algumas ferramentas de teste; você sempre deve usar interfaces e tudo deve ser criado de uma maneira que permita trocar o objeto baseado em interface por outro.

No entanto, existem ferramentas melhores - use o Microsoft Fakes (chamado Moles) que permite trocar qualquer objeto, mesmo estático e global. É necessária uma abordagem de nível mais baixo para substituir objetos, para que você não precise usar interfaces em todos os lugares, mantendo a maneira de escrever testes com os quais está acostumado.

gbjbaanb
fonte