Search code examples
c#.netasp.net-coredependency-injectionautomapper

How to mock dependencies for IValueResolver from Automapper in unit tests


After previous question I have a simple implementation of IValueResolver

public class FileLinkResolver : IValueResolver<Configuration, ConfigurationDto, string>
{
    private readonly IFileStorage _fileStorage;

    public FileLinkResolver(IFileStorage fileStorage)
    {
        _fileStorage = fileStorage;
    }

    public string Resolve(Configuration source, ConfigurationDto destination, string destMember, ResolutionContext context)
    {
        return _fileStorage.GetShortTemporaryLink(source.Path);
    }
}

and simple mapping profile

public class MappingProfile : Profile
{
    public MappingProfile()
    {
        CreateMap<Configuration, ConfigurationDto>()
            .ForMember(dest => dest.FilePath, opt => opt.MapFrom<FileLinkResolver>());
    }
}

For production it works as expected when the following setup

services.AddTransient<IFileStorage>(...);
services.AddAutoMapper(); 

is used and then in controller IMapper is injected.

In unit test I try to stub the mapper

var mapperStub = new Mapper(new MapperConfiguration(map => map.AddProfile(new MappingProfile())));

and when I run tests for method witch should return mapped dto, I got

AutoMapper.AutoMapperMappingException : Error mapping types.

Mapping types:
Configuration -> ConfigurationDto
DataAccess.Models.Configuration -> Dto.ConfigurationDto

Type Map configuration:
Configuration -> ConfigurationDto
DataAccess.Models.Configuration -> Dto.ConfigurationDto

Destination Member:
FilePath

---- System.MissingMethodException : No parameterless constructor defined for this object.

I've tried to add parameterless constructor to FileLinkResolver but then, NullReferenceException

This is the question: how to resolve dependencies for ValueResolver


Solution

  • In the current example, the mapper is unable to resolve IFileStorage dependency when testing.

    Change the way the mapper is created to more closely match how it is done at run-time.

    IServiceCollection services = new ServiceCollection();
    //mocking service using MOQ
    var mock = Mock.Of<IFileStorage>(_ => 
        _.GetShortTemporaryLink(It.IsAny<string>()) == "fake link"
    );
    //adding mock to service collection.
    services.AddTransient<IFileStorage>(sp => mock);
    
    //adding auto mapper with desired profile;
    services.AddAutoMapper(typeof(MappingProfile));
    
    //...add other dependencies as needed to service collection.
    
    //...
    
    //build provider
    IServiceProvider serviceProvider = services.BuildServiceProvider();
    
    //resolve mapper
    IMapper mapperStub = serviceProvider.GetService<IMapper>();
    
    //Or resolve subject under test where mapper is to be injected
    SubjectClass subject = serviceProvider.GetService<SubjectClass>();
    //assuming ctr: public SubjectClass(IMapper mapper, .....) { ... }
    

    Now technically this is not mocking the value resolver. It mocks the dependencies of the resolver, and uses an actual resolver from the profile. But this should provide the desired behavior when testing the target.