Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to mock custom ValueResolver constructor parameters

I have a project in .NET Core and I'm using AutoMapper to map between my types. But I got into a problem, where I would like to mock the parameters of my custom value resolvers. I created dummy example to show the problem:

Let's say, that I have a ValueResolver, that needs to have access to DB to resolve the given member:

public class NumberProfile : Profile
{
    public NumberProfile()
    {
        CreateMap<NumberModel, NumberDto>()
            .ForMember(dest => dest.Number, opt => opt.MapFrom<NumberValueResolver>());
    }
}

public class NumberValueResolver : IValueResolver<NumberModel, NumberDto, int>
{
    private readonly IRepository _repository;

    public NumberValueResolver(IRepository repository)
    {
        _repository = repository;
    }

    public int Resolve(NumberModel source, NumberDto destination, int destMember, ResolutionContext context)
    {
        return _repository.GetNumber().Number + 3;
    }
}

Let's imagine, there is logic in Resolve method, that needs to be tested and I want to mock the repository to able to test it. This is what I tried in my tests

var repositoryMock = new Mock<IRepository>();
repositoryMock.Setup(s => s.GetNumber()).Returns(new NumberModel { Number = 3 });

var mapper = new MapperConfiguration(mc =>
{
    mc.AddProfile<NumberProfile>();
    mc.ConstructServicesUsing(s => new NumberValueResolver(repositoryMock.Object));
});

var service = new Service(repositoryMock.Object, mapper.CreateMapper());

And then I call a method on service that triggers the mapping. This case is good, it works and the test passes. The problem comes when I add another ValueResolver.

public class NumberProfile : Profile
{
    public NumberProfile()
    {
        CreateMap<NumberModel, NumberDto>()
            .ForMember(dest => dest.Number, opt => opt.MapFrom<NumberValueResolver>())
            .ForMember(dest => dest.AnotherNumber, opt => opt.MapFrom<AnotherNumberValueResolver>());
    }
}

public class NumberValueResolver : IValueResolver<NumberModel, NumberDto, int>
{
    private readonly IRepository _repository;

    public NumberValueResolver(IRepository repository)
    {
        _repository = repository;
    }

    public int Resolve(NumberModel source, NumberDto destination, int destMember, ResolutionContext context)
    {
        return _repository.GetNumber().Number + 3;
    }
}

public class AnotherNumberValueResolver : IValueResolver<NumberModel, NumberDto, string>
{
    public int Resolve(NumberModel source, NumberDto destination, string destMember, ResolutionContext context)
    {
        return $"{source.Number} number(s)";
    }
}

And then I thought I would just do this in my tests

var repositoryMock = new Mock<IRepository>();
repositoryMock.Setup(s => s.GetNumber()).Returns(new NumberModel { Number = 3 });

var mapper = new MapperConfiguration(mc =>
{
    mc.AddProfile<NumberProfile>();
    mc.ConstructServicesUsing(s => new NumberValueResolver(repositoryMock.Object));
    mc.ConstructServicesUsing(s => new AnotherNumberValueResolver());
});

var service = new Service(repositoryMock.Object, mapper.CreateMapper());

But then I get error:

System.InvalidCastException : Unable to cast object of type 'AutoMapper_app.ValueResolvers.AnotherNumberValueResolver' to type 'AutoMapper_app.ValueResolvers.NumberValueResolver'.

Did I misunderstand usage of ConstructServicesUsing or ValueResolvers or is it something completely different? Am I missing something super obvious? Is it maybe bad design?

like image 442
Denco Avatar asked Jan 29 '26 19:01

Denco


2 Answers

Had the same issue. After researching what is expected is a function delegate that returns a resolver based on the type (of expected resolver). So something like

x.ConstructServicesUsing(t =>
{
  if (t.Name.Contains("SomeObjectTypeResolver"))
    return new SomeObjectTypeResolver();
  // add more if statements if you have more than 2 resolvers
  return new SomeOtherObjectTypeResolver();
});
like image 90
Big D Avatar answered Jan 31 '26 07:01

Big D


This is where I use Moq.AutoMocker in my unit tests. It is used as an IoC container where you register your mocks in the container then ask AutoMocker to construct an object of the required type and the mocks will be injected for the constructor parameters.

Then you just need to configure AutoMapper to use AutoMocker to construct it's services so the same behaviour is used for your resolvers. This is where you were already using ConstructServicesUsing but instead of creating the resolver yourself use AutoMocker to construct an object of the requested type.

I have updated your example code above to show how it could be done with AutoMocker

var repositoryMock = new Mock<IRepository>();
repositoryMock.Setup(s => s.GetNumber()).Returns(new NumberModel { Number = 3 });

var autoMocker = new AutoMock();

var mapper = new MapperConfiguration(mc =>
{
    mc.AddProfile<NumberProfile>();
    mc.ConstructServicesUsing(type => autoMocker.CreateInstance(type));
}).CreateMapper();

autoMocker.Use(mapper);
autoMocker.Use(repositoryMock.Object);

var service = autoMocker.CreateInstance<Service>();
like image 39
blahDL Avatar answered Jan 31 '26 09:01

blahDL



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!