为 AutoFixture 提供真正的 AutoMapper 实例

Provide real AutoMapper instance to AutoFixture

我在单元测试中使用了 AutoFixture。 SUT 正在使用 AutoMapper。 在执行单元测试期间,我注意到 IMapper(AutoMapper 的接口)被 AutoFixture 模拟,因此忽略了我代码中的任何映射配置文件。

所以我写了一个定制能够传入一个IMapper实例,这样AutoFixture就不会模拟这个接口。 但现在看起来 AutoFixture 也不会模拟任何其他接口。在我引入 AutoMapper 自定义之前,这曾经有效。

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.

这是我现在拥有的。如您所见,我仍在使用 AutoMoq 自定义。 我的 AutoMapperDataAttribute 正在更新固定装置,因此 AutoMoq 自定义可能不会应用。 是否可以使用现有的 Fixture 实例而不是在我的属性中重新创建它?我也可以在 AutoMapperDataAttribute 中添加 AutoMoq 自定义,但最好将 AutoMoq 和 AutoMapper 行为分开。

单元测试:

[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 自定义:

    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);
        }
    }

AutoFixture 只能在允许自身扩展的情况下构建在测试框架上。 在 xUnit 2 中,每个提供的 DataAttribute 都被认为是测试参数的不同提供者,这就是它们在运行时被隔离的原因。

此处推荐的选项是创建一个将同时应用 AutoMoq 和 AutoMapper 自定义的数据属性。

您可以通过使用 CompositeCustomization 然后在自定义 AutoDataAttribute 实现中使用它来实现。

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

您想要在 AutoFixture 中实现的关注点分离应该来自具有小型可组合和可重用的自定义设置。

另一种鲜为人知的自定义夹具的方法是通过参数自定义。

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

这允许自定义灯具创建特定参数的方式。

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

碰巧最近我发布了一个实现了 AutoMapper 集成的包 AutoFixture.Community.AutoMapper。也许您会发现它很有用,或者您想复制它的某些功能。 这是 link 到 respository .