Search code examples
c#automapperautofixture

Provide real AutoMapper instance to AutoFixture


I'm using AutoFixture in my unit tests. The SUT is using AutoMapper. During execution of the unit test, I noticed that IMapper (interface of AutoMapper) was getting mocked by AutoFixture and thus disregarding any mapping profiles I have in my code.

So I wrote a customization to be able to pass in an IMapper instance so that AutoFixture will not mock this interface. But now it looks like that AutoFixture won't mock any other interfaces as well. This used to work before I introduced the AutoMapper customization.

AutoFixture.ObjectCreationExceptionWithPath : AutoFixture was unable to create an instance from Microsoft.Extensions.Logging.ILogger`1[MyAPI.Controllers.CustomerController] because it's an interface. There's no single, most appropriate way to create an object implementing the interface, but you can help AutoFixture figure it out.
    
    If you have a concrete class implementing the interface, you can map the interface to that class:
    
    fixture.Customizations.Add(
        new TypeRelay(
            typeof(Microsoft.Extensions.Logging.ILogger`1[MyAPI.Controllers.CustomerController]),
            typeof(YourConcreteImplementation)));
    
    Alternatively, you can turn AutoFixture into an Auto-Mocking Container using your favourite dynamic mocking library, such as Moq, FakeItEasy, NSubstitute, and others. As an example, to use Moq, you can customize AutoFixture like this:
    
    fixture.Customize(new AutoMoqCustomization());
    
    See http://blog.ploeh.dk/2010/08/19/AutoFixtureasanauto-mockingcontainer for more details.

Here's what I have now. As can you see, I'm still using the AutoMoq customization as well. My AutoMapperDataAttribute is newing up the fixture so the AutoMoq customization will not be applied presumably. Is it possible to work with an existing Fixture instance rather than recreating it in my attribute? I could add the AutoMoq customization in AutoMapperDataAttribute as well, but it would be better to separate the AutoMoq and AutoMapper behavior.

Unit test:

[Theory, AutoMoqData, AutoMapperData(typeof(MappingProfile))]
public async Task GetCustomers_ShouldReturnCustomers_WhenGivenCustomers(
  [Frozen] Mock<IMediator> mockMediator,
  List<CustomerDto> customers,
  [Greedy] CustomerController sut,
  GetCustomersQuery query
)
{
    //Arrange
    var dto = new GetCustomersDto
    {
      TotalCustomers = customers.Count,
      Customers = customers
    };

    mockMediator.Setup(x => x.Send(It.IsAny<GetCustomersQuery>(), It.IsAny<CancellationToken>()))
    .ReturnsAsync(dto);

    //Act
    var actionResult = await sut.GetCustomers(query);

    //Assert
    var okObjectResult = actionResult as OkObjectResult;
    okObjectResult.Should().NotBeNull();

    var response = okObjectResult.Value as Models.GetCustomers.GetCustomersResult;
    response.TotalCustomers.Should().Be(customers.Count);
    response.Customers.Count.Should().Be(customers.Count);
    response.Customers[0].AccountNumber.Should().Be(customers[0].AccountNumber);
    response.Customers[1].AccountNumber.Should().Be(customers[1].AccountNumber);
}

AutoMapper customization:

    public class AutoMapperDataAttribute : AutoDataAttribute
    {
        public AutoMapperDataAttribute(Type type)
          : base(() => new Fixture().Customize(new AutoMapperCustomization(CreateMapper(type))))
        {
        }

        private static IMapper CreateMapper(Type profileType)
        {
            return new MapperConfiguration(opts =>
            {
                opts.AddProfile(profileType);
            })
            .CreateMapper();
        }
    }

    public class AutoMapperCustomization : ICustomization
    {
        private readonly IMapper mapper;
        public AutoMapperCustomization(IMapper mapper) => this.mapper = mapper;

        public void Customize(IFixture fixture)
        {
            fixture.Inject(mapper);
        }
    }

Solution

  • AutoFixture can only build upon the testing framework only as much as it allows itself to be extended. In xUnit 2 each provided DataAttribute is considered, a distinct provider of arguments for the test, which is why they are isolated during runtime.

    The recommended option here is to create a data attribute that will apply both AutoMoq and the AutoMapper customization.

    You can achieve this by using a CompositeCustomization and then using it in a custom AutoDataAttribute implementation.

    public class AutoMappedDomainDataCustomization : CompositeCustomization
    {
        public AutoMappedDomainDataCustomization()
            : base(
                    new AutoMoqCustomization(),
                    new AutoMapperCustomization(typeof(TestProfile)))
        {
        }
    }
    

    The separation of concerns, that you want to achieve, in AutoFixture, should come from having small composable and reusable customizations.

    Another less known way to customize the fixture is via the parameter customization.

    [AttributeUsage(AttributeTargets.Parameter, AllowMultiple = false)]
    public class CustomerMapperAttribute : Attribute, IParameterCustomizationSource
    {
        public ICustomization GetCustomization(ParameterInfo parameter)
        {
            // Your implementation here
        }
    }
    

    This allows to customize the way specific parameters are created by the fixture.

    [Theory, AutoMockData]
    public void Foo([CustomerMapper][Frozen]IMapper mapper, MySut sut)
    {
    }
    

    It just so happens that just recently I published a package AutoFixture.Community.AutoMapper, that implements the AutoMapper integration. Perhaps you would find it useful or you'll want to copy some of its features. Here's the link to the respository .