如何测试 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
}
我的MyModelDTO
class:
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。
我想针对给定的 MyModelDTO
值测试我的控制器方法。
这是我的控制器Post方法(简化):
[HttpPost]
public ActionResult Post([FromBody] MyModelDTO itemDTO)
{
ModelState.Remove($"{nameof(itemDTO)}.{nameof(itemDTO.Id)}");
if (!ModelState.IsValid)
{
return BadRequest();
}
//rest of code
}
我的MyModelDTO
class:
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。