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
comments powered by Disqus