【问题标题】:How to mock ModelState.IsValid using the Moq framework?如何使用 Moq 框架模拟 ModelState.IsValid?
【发布时间】:2011-04-18 21:11:00
【问题描述】:

我在我的控制器操作方法中检查ModelState.IsValid,它创建了这样的员工:

[HttpPost]
public virtual ActionResult Create(EmployeeForm employeeForm)
{
    if (this.ModelState.IsValid)
    {
        IEmployee employee = this._uiFactoryInstance.Map(employeeForm);
        employee.Save();
    }

    // Etc.
}

我想在我的单元测试方法中使用 Moq 框架来模拟它。我试着像这样模拟它:

var modelState = new Mock<ModelStateDictionary>();
modelState.Setup(m => m.IsValid).Returns(true);

但这会在我的单元测试用例中引发异常。有谁能帮帮我吗?

【问题讨论】:

    标签: c# asp.net-mvc unit-testing mocking moq


    【解决方案1】:

    你不需要嘲笑它。如果您已经有控制器,则可以在初始化测试时添加模型状态错误:

    // arrange
    _controllerUnderTest.ModelState.AddModelError("key", "error message");
    
    // act
    // Now call the controller action and it will 
    // enter the (!ModelState.IsValid) condition
    var actual = _controllerUnderTest.Index();
    

    【讨论】:

    • 我们如何设置 ModelState.IsValid 以达到真实情况? ModelState 没有设置器,因此我们不能执行以下操作:_controllerUnderTest.ModelState.IsValid = true。没有它,它不会打击员工
    • @Newton,默认情况下是这样。您无需指定任何内容即可达到真实情况。如果您想遇到错误情况,只需添加一个模型状态错误,如我的回答所示。
    • 恕我直言,更好的解决方案是使用 mvc 输送机。通过这种方式,您可以获得更真实的控制器行为,您应该将模型验证交付给它的命运 - 属性验证。下面的帖子描述了这一点 (stackoverflow.com/a/5580363/572612)
    【解决方案2】:

    我对上述解决方案的唯一问题是,如果我设置属性,它实际上不会测试模型。我这样设置我的控制器。

    private HomeController GenerateController(object model)
        {
            HomeController controller = new HomeController()
            {
                RoleService = new MockRoleService(),
                MembershipService = new MockMembershipService()
            };
            MvcMockHelpers.SetFakeAuthenticatedControllerContext(controller);
    
            // bind errors modelstate to the controller
            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);
            controller.ModelState.Clear();
            controller.ModelState.Merge(modelBinder.ModelState);
            return controller;
        }
    

    modelBinder 对象是测试模型有效性的对象。这样我就可以设置对象的值并对其进行测试。

    【讨论】:

    • 非常好,这正是我想要的。我不知道有多少人在这样的老问题上发帖,但它对我来说很有价值。谢谢。
    • 似乎是一个很好的解决方案,但仍在 2016 年 :)
    • 用这样的东西单独测试模型不是更好吗? stackoverflow.com/a/4331964/3198973
    • 虽然这是一个聪明的解决方案,但我同意@Ru​​bberDuck。为了这是一个实际的、独立的单元测试,模型的验证应该是它自己的测试,而控制器的测试应该有它自己的测试。如果模型更改为违反 ModelBinder 验证,您的控制器测试将失败,这是一个误报,因为控制器逻辑没有被破坏。要测试无效的 ModelStateDictionary,只需添加一个虚假的 ModelState 错误以使 ModelState.IsValid 检查失败。
    【解决方案3】:

    uadrive 的回答带我走了一部分路,但仍有一些差距。在new NameValueCollectionValueProvider() 的输入中没有任何数据,模型绑定器会将控制器绑定到一个空模型,而不是model 对象。

    没关系——只需将您的模型序列化为NameValueCollection,然后将其传递给NameValueCollectionValueProvider 构造函数。嗯,不完全是。不幸的是,它在我的情况下不起作用,因为我的模型包含一个集合,而 NameValueCollectionValueProvider 不能很好地与集合一起使用。

    不过,JsonValueProviderFactory 可以在这里进行救援。 DefaultModelBinder只要指定内容类型为"application/json" 并将序列化的JSON对象传递到请求的输入流中即可使用(请注意,因为这个输入流是内存流,所以可以离开它没有被释放,因为内存流不保留任何外部资源):

    protected void BindModel<TModel>(Controller controller, TModel viewModel)
    {
        var controllerContext = SetUpControllerContext(controller, viewModel);
        var bindingContext = new ModelBindingContext
        {
            ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(() => viewModel, typeof(TModel)),
            ValueProvider = new JsonValueProviderFactory().GetValueProvider(controllerContext)
        };
    
        new DefaultModelBinder().BindModel(controller.ControllerContext, bindingContext);
        controller.ModelState.Clear();
        controller.ModelState.Merge(bindingContext.ModelState);
    }
    
    private static ControllerContext SetUpControllerContext<TModel>(Controller controller, TModel viewModel)
    {
        var controllerContext = A.Fake<ControllerContext>();
        controller.ControllerContext = controllerContext;
        var json = new JavaScriptSerializer().Serialize(viewModel);
        A.CallTo(() => controllerContext.Controller).Returns(controller);
        A.CallTo(() => controllerContext.HttpContext.Request.InputStream).Returns(new MemoryStream(Encoding.UTF8.GetBytes(json)));
        A.CallTo(() => controllerContext.HttpContext.Request.ContentType).Returns("application/json");
        return controllerContext;
    }
    

    【讨论】:

      猜你喜欢
      • 2019-01-08
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-09-16
      • 1970-01-01
      • 1970-01-01
      • 2012-03-18
      • 2010-10-19
      相关资源
      最近更新 更多