对抛出异常的控制器进行单元测试

Iva*_*ono 5 c# moq xunit.net autofixture asp.net-core

我有一个具有以下签名的控制器:

public CustomerTypeController(
    IHttpContextAccessor accessor,
    IPrincipalProvider provider,
    IMapper mapper, 
    ILogger<CustomerTypeController> logger,
    ICustomerTypeService customerTypeService)
{ }
Run Code Online (Sandbox Code Playgroud)

现在我的Theory样子是这样的:

[Theory, AutoMoqData]
public void GetWhenHasCustomerTypesShouldReturnOneCustomerType(
    IFixture fixture,
    [Frozen] Mock<ICustomerTypeService> service,
    CustomerTypeController sut)
{
    //Arrange
    var items = fixture.CreateMany<Model.CustomerType>(3).ToList();

    //Act
    var result = sut.Get(1);

    //Assert
    Assert.IsType<OkResult>(result);
}
Run Code Online (Sandbox Code Playgroud)

当我按原样运行此测试时,出现以下异常:

AutoFixture.ObjectCreationExceptionWithPath : AutoFixture 无法从 Microsoft.AspNetCore.Mvc.ModelBinding.BindingInfo 创建实例,因为创建意外失败并出现异常。请参考内部异常来排查失败的根本原因。

内部异常消息:System.Reflection.TargetInitationException:调用目标已引发异常。System.ArgumentException:类型“System.Object”必须实现“Microsoft.AspNetCore.Mvc.ModelBinding.IModelBinder”才能用作模型绑定器。(参数“值”)

我做错了什么,我该如何解决这个问题?

And*_*scu 6

长话短说

您应该使用属性来装饰测试方法中的控制器参数[NoAutoProperties]

[Theory, AutoMoqData]
public void GetWhenHasCustomerTypesShouldReturnOneCustomerType(
    IFixture fixture,
    [Frozen] Mock<ICustomerTypeService> service,
    [NoAutoProperties] CustomerTypeController sut)
{
    //Arrange
    var items = fixture.CreateMany<Model.CustomerType>(3).ToList();

    //Act
    var result = sut.Get(1);

    //Assert
    Assert.IsType<OkResult>(result);
}
Run Code Online (Sandbox Code Playgroud)

更新

现在我对 AutoFixture 代码库有了更好的了解,我想了解为什么这实际上可以解决问题。

Greedy属性通常指示 AutoFixture 使用参数最多的构造函数,这应该与修复无关。

正如错误消息所述,当设置属性并且该属性期望实现IModelBinder. 错误的根源是BinderType该类的属性BindingInfo,该属性的类型为System.Type。默认情况下,AutoFixture 将解析TypeSystem.Object,这解释了错误消息。

应用该Greedy属性时,会使用自定义工厂自定义 AutoFixture 以创建属性类型的实例。生成的构建器图节点(可能是意外)会跳过在创建的实例上设置任何属性。

考虑到这一点,更合适的解决方案应该是使用该NoAutoProperties属性。这将显式指示 AutoFixture 忽略修饰类型中的所有自动属性,但会将构造函数查询保留为“适度”。

由于在任何地方添加属性可能会变得烦人和乏味,因此我建议自定义 AutoFixture 以忽略ControllerBase域自定义中的所有属性。另外,如果您使用属性注入,这将允许 AutoFixture 实例化控制器属性。

public class AutoMoqDataAttribute : AutoDataAttribute
{
    public AutoMoqDataAttribute()
        : base(() => new Fixture().Customize(
            new CompositeCustomization(
                new AutoMoqCustomization(),
                new AspNetCustomization())))
    {
    }
}

public class AspNetCustomization : ICustomization
{
    public void Customize(IFixture fixture)
    {
        fixture.Customizations.Add(new ControllerBasePropertyOmitter());
    }
}

public class ControllerBasePropertyOmitter : Omitter
{
    public ControllerBasePropertyOmitter()
        : base(new OrRequestSpecification(GetPropertySpecifications()))
    {
    }

    private static IEnumerable<IRequestSpecification> GetPropertySpecifications()
    {
        return typeof(ControllerBase).GetProperties().Where(x => x.CanWrite)
            .Select(x => new PropertySpecification(x.PropertyType, x.Name));
    }
}
Run Code Online (Sandbox Code Playgroud)

如果您出于某种原因需要这些属性,ControllerBase那么只需指示 AutoFixture 如何正确创建BindingInfo实例即可。


原答案

您应该使用属性来装饰测试方法中的控制器参数[Greedy]

[Theory, AutoMoqData]
public void GetWhenHasCustomerTypesShouldReturnOneCustomerType(
    IFixture fixture,
    [Frozen] Mock<ICustomerTypeService> service,
    [Greedy] CustomerTypeController sut)
{
    //Arrange
    var items = fixture.CreateMany<Model.CustomerType>(3).ToList();

    //Act
    var result = sut.Get(1);

    //Assert
    Assert.IsType<OkResult>(result);
}
Run Code Online (Sandbox Code Playgroud)