unit-testingasp.net-web-apimoqautomapperstructuremap

How to Mock an AutoMapper IMapper object in Web API Tests With StructureMap Dependency Injection?


So I've build a WebAPI from scratch, including some best practices that I've found online such as Dependency Injection and Domain<->DTO mapping using auto mapper etc.

My API Controllers now look similar to this

public MyController(IMapper mapper)
{
}

and AutoMapper Registry:

public AutoMapperRegistry()
{
    var profiles = from t in typeof(AutoMapperRegistry).Assembly.GetTypes()
                    where typeof(Profile).IsAssignableFrom(t)
                    select (Profile)Activator.CreateInstance(t);
    
    var config = new MapperConfiguration(cfg =>
    {
        foreach (var profile in profiles)
        {
            cfg.AddProfile(profile);
        }
    });

    For<MapperConfiguration>().Use(config);
    For<IMapper>().Use(ctx => ctx.GetInstance<MapperConfiguration>().CreateMapper(ctx.GetInstance));
}

I'm also building a few test cases, implementing MOQ, and this is where i feel a little unsure. whenever calling my controllers, I need to pass in an IMapper like this:

var mockMapper = new Mock<IMapper>();
var controller = new MyController(mockMapper.Object);

But then, how do i configure the IMapper to have the correct mappings? It feels redundant to recreate the same logic I've already created before to configure the Mapper. so I am wondering what is the recommended approach to do this?


Solution

  • That's pretty simple: if you mock IMapper and imagine it as a fully abstract concept of mapping data from one object to another, then you have to treat is an abstraction and not imply there's a real automapper behind it.

    First you should not register any existing profile at all, you should instead setup IMapper.Map method to return specific object when given another object.

    So for each profile used for specific method you have to do a setup, looking approximately like this:

    var mockMapper = new Mock<IMapper>();
    mockMapper.Setup(x => x.Map<DestinationClass>(It.IsAny<SourceClass>()))
        .Returns((SourceClass source) =>
            {
                // abstract mapping function code here, return instance of DestinationClass
            });
    

    In this case, your test knows nothing about actual IMapper implementation - it just uses it methods to get the data you expect from actual IMapper implementation to receive.