如何测试 Core api 控制器的自动 ModelState 自定义属性方法验证?

How to test a automatic ModelState's custom attribute method validation for Core api controller?

我想针对给定的 MyModelDTO 值测试我的控制器方法。

这是我的控制器Post方法(简化):

[HttpPost]
public ActionResult Post([FromBody] MyModelDTO itemDTO)
{
    ModelState.Remove($"{nameof(itemDTO)}.{nameof(itemDTO.Id)}");
    if (!ModelState.IsValid)
    {
        return BadRequest();
    }
    //rest of code
}

我的MyModelDTOclass:

public class MyModelDTO
{
    [IsNotEmpty(ErrorMessage = "Guid Id Is Empty")]
    public Guid Id { get; set; }
}

我的习惯ValidationAttribute:

public class IsNotEmptyAttribute : ValidationAttribute
{
    public override bool IsValid(object value)
    {
        if (value == null) return false;

        var valueType = value.GetType();
        var emptyField = valueType.GetField("Empty");

        if (emptyField == null) return true;

        var emptyValue = emptyField.GetValue(null);

        return !value.Equals(emptyValue);
    }
}

我的问题是如何测试 ModelState 自定义属性的自动验证

这是我试过的:

[Test] public void Post_WhenCalled_ShouldReturnPostResult()
{
    using (var mock = AutoMock.GetLoose())
    {
        //Arrange
        var controller = mock.Create<MyController>();

        //Act
        ActionResult actionResult = controller.Post(new MyModelDTO());

        //Assert... 
     }
}

单元测试工作正常(控制器应该使用参数 MyModelDTO 而没有 Id),但看起来它并没有真正模拟 ModelState 的自动验证过程。我怎么知道的?因为当我尝试做一个缺少 Id 属性 的邮递员时,它会产生 "Guid Id Is Empty" 消息。它甚至不会在断点处停止。

您有两个选择:可以对其进行单元测试,也可以对其进行集成测试。现在的问题是您试图混合使用这两种方法。

要进行适当的单元测试,您只需实例化属性本身并将数据传递给 IsValid 以确保它 returns true/false 正确。不需要控制器或其他任何东西。您实际上只是在测试执行验证的实际方法,以确保它正确执行。

如果你想全面测试它,确保它在管道内部请求传递数据的实际情况下工作,那么你需要做一个集成测试,但这需要测试服务器的用户并实际使用测试客户端发出真正的请求(这只是一个 HttpClient 实例。

仅仅新建一个控制器并像方法一样调用一个动作是不够的。除其他差异外,它不涉及模型绑定器,因此也不涉及任何验证机制。总之,您的属性不起作用,因为它从未被调用过。

模型验证作为模型绑定过程的一部分,在控制器外部调用。这意味着您无法在控制器单元测试中真正测试它。相反,在测试控制器时,如果您想根据模型状态验证控制器的行为,您基本上已经必须模拟模型状态。

您基本上可以在这里做两件事:如果您只想测试您的验证逻辑,那么最好的方法就是直接调用 ValidationAttribute。所以你不测试控制器,而是测试你的属性。

您可以简单地实例化您的属性,然后 运行 Validate 方法来测试它的行为。只要传一个你要验证的对象的实例,你就可以验证它抛出的异常。

另一种解决方案是做一个完整的 integration test。这样,您就不会对控制器进行单元测试,而是测试整个请求管道,包括控制器和模型验证。对于特定场景,这是确保一切正常的最佳方式end-to-end。