Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Upgrading AutoMapper from v6 to v9 and unit testing with Resolution Context

Tags:

automapper-9

I'm hoping that somebody can help me. We are currently upgrading AutoMapper from v6 to v9 - we would go to v10 but the inability to create new ResolutionContext impacts our unit testing. That said with v9 we are still having the following issue with unit testing AutoMapper Converters...

A ConverterClass:

public class FooBarConverter :
    ITypeConverter<FooSourceObject, BarDestinationObject>
{
    /// <inheritdoc/>
    public virtual BarDestinationObjectConvert(FooSourceObjectsource, BarDestinationObjectdestination, ResolutionContext context)
    {
        EnsureArg.IsNotNull(source, nameof(source));

        switch (source.Type)
        {
            case SomeType.None:
                return context.Mapper.Map<NoneBarDestinationObject>(source);
            case SomeType.FixedAmount:
                return context.Mapper.Map<FixedBarDestinationObject>(source);
            case SomeType.Percentage:
                return context.Mapper.Map<PercentageBarDestinationObject>(source);
            default:
                throw new ArgumentOutOfRangeException(nameof(source));
        }
    }

Before in AutoMapper 6 we had the following Unit Test (using Xunit):

public class FooBarConverterTests
{
    private readonly FooBarConverter target;

    private readonly Mock<IRuntimeMapper> mockMapper;
    private readonly ResolutionContext resolutionContext;

    public FooBarConverterTests()
    {
        this.mockMapper = new Mock<IRuntimeMapper>();
        this.resolutionContext = new ResolutionContext(null, this.mockMapper.Object);
        this.target = new FooBarConverter();
    }

    [Fact]
    public void FixedAmountFooModel_ConvertsTo_FixedBarDomainModel()
    {
        // Arrange
        var input = new Foo
        {
            Type = SomeType.FixedAmount
        };

        var expected = new DomainModels.FixedBarDestinationObject();

        this.mockMapper.Setup(x => x.Map<FixedBarDestinationObject>(It.IsAny<FooSourceObjectsource>()))
            .Returns(expected);

        // Act
        var actual = this.target.Convert(input, It.IsAny<BarDestinationObjectdestination>(), this.resolutionContext);

        // Assert
        actual.ShouldSatisfyAllConditions(
            () => actual.ShouldNotBeNull(),
            () => actual.ShouldBeAssignableTo<DomainModels.FixedBarDestinationObject>());

        this.mockMapper.Verify(x => x.Map<DomainModels.FixedBarDestinationObject>(input));
    }
}

Essentially, this was working fine, however since upgrading to v9, the mapping setup goes missing as it's passed through the resolution context. Meaning that the resulting call of Mapper.Map<FixedBarDestinationObject>() always returns null.

I understand that the ResolutionContext may have changed slightly, but I don't understand how to resolve this issue and ensure that the mock mapping is passed through to the underlying converter.

Thank you for any help or advice.

like image 725
wombat172a Avatar asked Oct 23 '25 22:10

wombat172a


1 Answers

Thanks to Lucian I finally got my head around this:

public class FooBarConverterTests
{
    private readonly FooBarConverter target;

    private readonly IMapper mapper;

    public FooBarConverterTests()
    {
        this.mapper = this.GetMapperConfiguration().CreateMapper();
        
        this.target = new FooBarConverter();
    }

    [Fact]
    public void FixedAmountFooModel_ConvertsTo_FixedBarDomainModel()
    {
        // Arrange
        var input = new Foo
        {
            Type = SomeType.FixedAmount
        };

        var expected = new DomainModels.FixedBarDestinationObject();

        // Act
        var actual = this.Mapper.Map<BarDestinationObjectdestination>(input);

        // Assert
        actual.ShouldSatisfyAllConditions(
            () => actual.ShouldNotBeNull(),
            () => actual.ShouldBeAssignableTo<DomainModels.FixedBarDestinationObject>());
    }
    
        private MapperConfiguration GetMapperConfiguration()
        {
            return new MapperConfiguration(opt =>
            {
                opt.AddProfile<CustomAutomapperProfile>();
                opt.ConstructServicesUsing(t =>
                {
                    if (t == typeof(FooBarConverter))
                    {
                        return this.target;
                    }

                    return null;
                });
            });
        }
}

So, I'm loading the mapper profile (which requires the converter) and call the converter through that, this ensures that the mapper profile is loaded.

As a bonus, this also means that I entirely do away with newing up the ResolutionContext and paves the way for upgrading to v10.

like image 188
wombat172a Avatar answered Oct 27 '25 00:10

wombat172a