C# 如何测试ModelState?

C# 如何测试ModelState?,c#,asp.net-mvc,C#,Asp.net Mvc,如何测试控制器.ViewData.ModelState?我更愿意在没有任何模拟框架的情况下完成它 当然,如果您对数据使用存储库模式,就不必使用Mock 一些例子: 除了上面的好答案之外,还可以查看Controller类中受保护的TryValidateModel方法的奇妙用法 只需创建一个从controller继承的测试类,并将模型传递给TryValidateModel方法。以下是链接: John Reilly和Marc Talary获得了此解决方案的全部荣誉。要测试Web API,请使用控制

如何测试
控制器.ViewData.ModelState
?我更愿意在没有任何模拟框架的情况下完成它

当然,如果您对数据使用存储库模式,就不必使用Mock

一些例子:


除了上面的好答案之外,还可以查看Controller类中受保护的TryValidateModel方法的奇妙用法

只需创建一个从controller继承的测试类,并将模型传递给TryValidateModel方法。以下是链接:


John Reilly和Marc Talary获得了此解决方案的全部荣誉。

要测试Web API,请使用控制器上的方法:

var controller=new MyController();
controller.Configuration=新的HttpConfiguration();
var模型=新的MyModel();
控制器。验证(模型);
var结果=controller.MyMethod(模型);

在.NetCore 2.1中遇到此问题 以下是我的解决方案:

扩展方法

using Microsoft.AspNetCore.Mvc;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;

namespace MyExtension
{
    public static void BindViewModel<T>(this Controller controller, T model)
    {
        if (model == null) return;

        var context = new ValidationContext(model, null, null);
        var results = new List<ValidationResult>();

        if (!Validator.TryValidateObject(model, context, results, true))
        {
            controller.ModelState.Clear();
            foreach (ValidationResult result in results)
            {
                var key = result.MemberNames.FirstOrDefault() ?? "";
                controller.ModelState.AddModelError(key, result.ErrorMessage);
            }
        }
    }
}
单元测试

public async void MyUnitTest()
{
    // helper method to create instance of the Controller
    var controller = this.CreateController();

    var model = new MyViewModel
    {
        Name = null
    };

    // here we call the extension method to validate the model
    // and set the errors to the Controller's ModelState
    controller.BindViewModel(model);

    var result = await controller.ActionName(model);

    Assert.NotNull(result);
    var viewResult = Assert.IsType<BadRequestObjectResult>(result);
}
public异步void MyUnitTest()
{
//创建控制器实例的助手方法
var controller=this.CreateController();
var模型=新的MyViewModel
{
Name=null
};
//这里我们调用扩展方法来验证模型
//并将错误设置为控制器的ModelState
controller.BindViewModel(模型);
var result=await controller.ActionName(模型);
Assert.NotNull(结果);
var viewResult=Assert.IsType(结果);
}

这不仅可以让您检查错误是否存在,还可以检查它是否具有与预期完全相同的错误消息。例如,这两个参数都是必需的,因此它们的错误消息显示为“必需”

模型标记:

//[Required]
//public string Name { get; set; }
//[Required]
//public string Description { get; set; }
单元测试代码:

ProductModelEdit model = new ProductModelEdit() ;
//Init ModelState
var modelBinder = new ModelBindingContext()
{
    ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(
                      () => model, model.GetType()),
    ValueProvider=new NameValueCollectionValueProvider(
                        new NameValueCollection(), CultureInfo.InvariantCulture)
};
var binder=new DefaultModelBinder().BindModel(
                 new ControllerContext(),modelBinder );
ProductController.ModelState.Clear();
ProductController.ModelState.Merge(modelBinder.ModelState);

ViewResult result = (ViewResult)ProductController.CreateProduct(null,model);
Assert.IsTrue(!result.ViewData.ModelState.IsValid);
//Make sure Name has correct errors
Assert.IsTrue(result.ViewData.ModelState["Name"].Errors.Count > 0);
Assert.AreEqual(result.ViewData.ModelState["Name"].Errors[0].ErrorMessage, "Required");
//Make sure Description has correct errors
Assert.IsTrue(result.ViewData.ModelState["Description"].Errors.Count > 0);
Assert.AreEqual(result.ViewData.ModelState["Description"].Errors[0].ErrorMessage, "Required");

具体点。你想测试它做什么?对错误进行虚拟修改会很好,唉,有一个安排税,对我来说有点重。我想更好的解决方案是使用mvc传送带。通过这种方式,您可以获得更真实的控制器行为,您应该将模型验证交付给它的destiny属性验证。下面的帖子描述了这个()。Scott链接的文章似乎已经被破坏了,可以在其他地方找到吗?我真的很喜欢这个方法。正如您所说的,它在正确测试一个用验证属性修饰过的模型方面要好得多。非常方便。我想我大体上同意一些人的看法,他们说你不应该测试你的模型属性,因为这是框架的一部分,但是有时候,在未来的所有时间里,确认你的控制器正在捕获关键错误是至关重要的。试着为Web API找到等效的代码。如果有人知道,也许他们可以在这里更新?@KierenJohnstone我已经为Web API添加了一个答案。喜欢你的回答。您可以创建一个扩展方法
publicstaticvoidbindmodeltocontroller(此控制器,T模型){…}
请在此处包含解决方案,而不是链接到博客帖子您的链接已失效。这!为什么DNC2不能像这样开箱即用:(我只想做一个单元测试,确保
If!ModelState.IsValid,return BadRequest()
在控制器中,除非模型是IEnumerable,否则操作非常有效。我侵入了您的代码,检查模型是否是IEnumerable,然后在每个模型中循环执行TryValidationObject检查。@mobese46添加得很好!
public async void MyUnitTest()
{
    // helper method to create instance of the Controller
    var controller = this.CreateController();

    var model = new MyViewModel
    {
        Name = null
    };

    // here we call the extension method to validate the model
    // and set the errors to the Controller's ModelState
    controller.BindViewModel(model);

    var result = await controller.ActionName(model);

    Assert.NotNull(result);
    var viewResult = Assert.IsType<BadRequestObjectResult>(result);
}
//[Required]
//public string Name { get; set; }
//[Required]
//public string Description { get; set; }
ProductModelEdit model = new ProductModelEdit() ;
//Init ModelState
var modelBinder = new ModelBindingContext()
{
    ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(
                      () => model, model.GetType()),
    ValueProvider=new NameValueCollectionValueProvider(
                        new NameValueCollection(), CultureInfo.InvariantCulture)
};
var binder=new DefaultModelBinder().BindModel(
                 new ControllerContext(),modelBinder );
ProductController.ModelState.Clear();
ProductController.ModelState.Merge(modelBinder.ModelState);

ViewResult result = (ViewResult)ProductController.CreateProduct(null,model);
Assert.IsTrue(!result.ViewData.ModelState.IsValid);
//Make sure Name has correct errors
Assert.IsTrue(result.ViewData.ModelState["Name"].Errors.Count > 0);
Assert.AreEqual(result.ViewData.ModelState["Name"].Errors[0].ErrorMessage, "Required");
//Make sure Description has correct errors
Assert.IsTrue(result.ViewData.ModelState["Description"].Errors.Count > 0);
Assert.AreEqual(result.ViewData.ModelState["Description"].Errors[0].ErrorMessage, "Required");