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

2

22

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?

Youngran answered 18/3, 2016 at 0:54 Comment(3)
Almost certainly you don't need to mock it, and can just pass in the normal AutoMapper in the test setup: the mocked mapper won't actually map the objects anyway, it will just record that it was asked to do so and return what you tell it to.Cordelier
Tests is a different project. Normal mapper is defined in the base api project. wouldn't it defeat the purpose of seperation?Youngran
what are you trying to accomplish with your test at the Controller level? I agree with @Cordelier that you should not mock AutoMapper in you Controller tests. If you use the real AutoMapper implementation and setup, your test will help you catch AutoMapper mapping issues as well.Gripper
T
31

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.

Trogon answered 18/3, 2016 at 8:33 Comment(1)
Here is another referenceFluid
G
33

This might me another solution

//auto mapper configuration
var mockMapper = new MapperConfiguration(cfg =>
{
    cfg.AddProfile(new AutoMapperProfile()); //your automapperprofile 
});
var mapper = mockMapper.CreateMapper();

And then call then controller like so

var controller = new YourController(imapper:mapper,..otherobjects..);

This way it will serve the purpose or else if you create mock object for IMapper then it will return what you ask it to return.

Gere answered 18/6, 2020 at 7:26 Comment(1)
This one saved me . Thanks!Ethiopia
T
31

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.

Trogon answered 18/3, 2016 at 8:33 Comment(1)
Here is another referenceFluid

© 2022 - 2024 — McMap. All rights reserved.