How to isolate AutoMapper in Unit Tests?

Introduction

When it comes to Unit testing, the rule is to isolate all the dependencies and focus only on the unit of code you are testing. I have been silently ignoring this for AutoMapper until I found a proper way to isolate and replace the behavior of mapping.

When I was looking for a proper way to do this, I come across many posts but none of them were having full working example without any additional layer between the function/class that wants to use AutoMapper and AutoMapper itself.

IMappingEngine

AutoMapper comes with a static class Mapper which has almost all the methods we need to work with AutoMapper. I we want to create a new mapping, we can use Mapper.CreateMap() method (we can also use AutoMapper Profile instances) and if we want to perform mapping between pre-configured objects we can use Mapper.Map() method.

The Mapper class exposes a property Engine which holds all the mapping configurations. This property is of type IMappingEngine.

When we invoke Map method on Mapper class, it in-turn calls Map method of Engine property.

So, all we need to do to isolate and control the behavior of mapping in our unit test is to replace the Engine property with our own implementation.

But, how?

The Engine is static property in Mapper class which is also static. We know very well that it is nearly impossible to replace the implementation of static members without using some fancy tools or test patterns.

Fortunately, the type of Engine property is interface IMappingEngine. This means, we can declare IMappingEngine as a dependency of SUT and ask DI framework to provide it with the instance, in this case Mapper.Engine.

Here is how it looks like if you use Ninject

kernel.Bind<IMappingEngine>().ToConstant(Mapper.Engine);

And with ASP.NET 5 built-in DI

services.AddInstance<IMappingEngine>(Mapper.Engine);

In the unit test method, you need to create Test Double which implements IMappingEngine and create SUT with Test Double as input for dependency. The easiest way to create Test Double is to create it as a Mock using your favourite mocking framework.

Here is an example using Moq,

var mockMapper = new Mock<IMappingEngine>();

mockMapper.Setup(mock => mock.Map<TargetType>(It.IsAny<SourceType>())).Returns(targetInstance);

Full example

public class User
    {
        public int Id { get; set; }
        public string FirstName { get; set; }
        public string LastName { get; set; }
        public int Age { get; set; }
    }

    public class UserDto
    {
        public int Id { get; set; }
        public string FirstName { get; set; }

    }

    public class Sut
    {
        private readonly IMappingEngine _mapper;

        public Sut(IMappingEngine mapper)
        {
            _mapper = mapper;
        }

        public User Run(UserDto dto)
        {
            return _mapper.Map<User>(dto);
        }
    }

    public class SutTests
    {
        [Fact]
        public void RunShouldReturnUserCorrectly()
        {
            // arrange
            var stubUser = new User();
            var stubUserDto = new UserDto();
            var mockMapper = new Mock<IMappingEngine>();
            mockMapper.Setup(mock => mock.Map<User>(It.Is<UserDto>(stubUserDto))).Returns(stubUser);

            var target = new Sut(mockMapper.Object);

            // act
            var actual = target.Run(stubUserDto);

            // assert
            Assert.AreEquals(stubUser, actual);

        }
    }
comments powered by Disqus