我想测试给定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
班:
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"
消息。它甚至不会在断点处停止。
答案 0 :(得分:2)
您有两种选择:可以对其进行单元测试,也可以对其进行集成测试。现在的问题是,您正在尝试将两种方法混合使用。
要进行适当的单元测试,您只需实例化属性本身并将数据传递到IsValid
,以确保其正确返回true / false。不需要控制器或其他任何东西。本质上,您只是在测试执行验证的实际方法,以确保其正确执行。
如果要完全测试它,以确保它在从管道内部的请求传递数据的实际情况下可以正常工作,则需要进行集成测试,但这需要测试服务器的用户,并且实际上使用测试客户端(只是一个HttpClient
实例)发出真正的请求。
仅更新控制器并调用诸如方法之类的操作是不够的。除其他差异外,它不涉及模型绑定器,因此也不涉及任何验证机制。简而言之,您的属性无法使用,因为甚至从未调用过它。
答案 1 :(得分:2)
作为模型绑定过程的一部分,模型验证在控制器外部调用。这意味着您不能真正在控制器单元测试中对其进行测试。相反,在测试控制器时,如果您想基于模型状态验证控制器的行为,则基本上已经必须模拟模型状态。
您基本上可以在这里做两件事:如果您只想测试验证逻辑,那么最好的方法就是直接调用ValidationAttribute
。因此,您无需测试控制器,但可以测试属性。
您可以简单地实例化属性,然后运行Validate
方法以测试其行为。只需传递您要验证的对象的实例,就可以验证它引发的异常。
另一种解决方案是执行完整的integration test。这样,您就无需对控制器进行单元测试,而是对整个请求管道(包括控制器和模型验证)进行测试。对于特定情况,这是确保所有操作都能端到端的最佳方法。