Thursday, 21 June 2012

Mocking AutoMapper in Unit Testing

This post will show how to mock AutoMapper with Moq in unit testing. Unit testing that has dependency to AutoMapper would require all of the mapping configurations be specified and run first before the actual mapping takes place (Mapper.Map(...) is called). These configurations would be burdensome and should not be included in unit testing. A unit test for a feature should only test that particular feature the developer has written. It should not test other services or functionalities. This is where the concept of mocking come up.

To be able to mock AutoMapper, we can use Dependency Injection to inject a mapper interface to the constructor of the calling code's class rather than using AutoMapper directly. Below is an example of such interface:
public interface IMappingService
{
    TDest Map<TSrc, TDest>(TSrc source) where TDest : class;
}

Then create an implementation class for the interface. Note on line 5 that for this class we specify AutoMapper directly.
public class MappingService : IMappingService
{
    public TDest Map<TSrc, TDest>(TSrc source) where TDest : class
    {
        return AutoMapper.Mapper.Map<TSrc, TDest>(source);
    }
}

Next, bind the interface with the concrete class. Below is an example of how to do it with Ninject:
kernel.Bind<IMappingService>().To<MappingService>();

Then whenever we want to do mapping, we call the interface's Map method instead of using the AutoMapper's Mapper.Map() method.
public ViewResult List()
{
    IEnumerable<Stuff> stuffs = stuffRepository.All;

    List<StuffViewModel> model = mappingService.Map<IEnumerable<Stuff>, List<StuffViewModel>>(stuffs);

    return View("List", model);
}
Please remember that mapping configurations need to be specified first before we could do any mapping. You can see this post to see how to set up the configurations.

Now we can mock the mapper in our unit test. In the example below I use xUnit testing framework. As you can see; first we create a mock instance from the interface, setup what the Map method will return and then pass the mock object to the class constructor of the feature to be tested (line 6, 25 and 28).
[Fact]
public void ListPageReturnsStuffViewModels()
{
    // Arrange
    Mock<IStuffRepository> stuffRepository = new Mock<IStuffRepository>();
    Mock<IMappingService> mappingService = new Mock<IMappingService>();

    List<Stuff> stuffs = new List<Stuff>();
    stuffRepository.Setup(r => r.All).Returns(stuffs.AsQueryable());

    var viewModelStuffs = new List<StuffViewModel> {
        new StuffViewModel { StuffID = 1/*,
                                Name= "Bip",
                                Description= "Colourful baby bip",
                                DateAdded = DateTime.Now,
                                UserID = 1 */
        },
        new StuffViewModel { StuffID = 2/*,
                                Name= "Socks",
                                Description= "Winter socks with animal figures",
                                DateAdded = DateTime.Now,
                                UserID = 1 */
        }
    };
    mappingService.Setup(m => m.Map<IEnumerable<Stuff>, List<StuffViewModel>>(It.IsAny<IEnumerable<Stuff>>()))
                    .Returns(viewModelStuffs);

    var controller = new StuffsController(stuffRepository.Object, mappingService.Object);


    // Act
    var result = controller.List() as ViewResult;
    //var model = result.ViewData.Model as List<StuffViewModel>;


    // Assert
    var model = Assert.IsType<List<StuffViewModel>>(result.ViewData.Model);
    Assert.Equal(2, model.Count);                
}

3 comments:

Ali Raza said...

Many thanks. This is exactly what I was trying to do :)

bloparod said...

Another way to mock Automapper is to use the interface IMappingEngine. Automapper class has a property Engine that implements this interface.

rical said...

We could do that as well but we would need to include AutoMapper as a reference in the project. Usually we want our test project clean from any unused / not under test library reference..