How to Mock a Method with a Hard-Coded Object

mockingunit testing

I am working on an application which have multiple layers. Data access layer to retrieve and save data from data source, business logic to manipulate data, user interface to show the data on screen.

I also doing unit testing of the business logic layer. The only requirement is to test the flow of business layer logic. So I use Moq framework to mock the data access layer and unit test the business logic layer with MS Unit.

I am using interface programming to make the design decouple as much as possible so that unit test can be done. Business layer call data access layer through interface.

I am facing a problem when I am trying to test one of the business logic method. That method does some work and create an object and pass it to data access layer. When I am trying to mock that data access layer method then it can not successfully mock.

Here I am trying to create a demo code to show my problem.

Model:

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

Data access layer:

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

Business logic layer:

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

Unit test:

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

In unit test case at the time of mocking I am sending an Employee object but when invoking the business logic method, it is creating different Employee object inside the method. That is why I can not mock the object.

In that case how to design so that I can solve the problem?

Best Answer

Instead of creating an Employee object directly by using new, your class Bll could use an EmployeeFactory class for this, with a method createInstance, which is injected through the constructor:

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

The constructor should take the factory object through an interface IEmployeeFactory, so you can replace the "real" factory it easily by a mock factory.

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(); 
        // ...
    }
    //...
}

The mock factory can provide the test with any kind of Employee object you need for your test (for example, createInstance could always return the same object):

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

Now using this mock in your test should do the trick.