如何使用Moq框架模拟ModelState.IsValid?

时间:2010-09-29 14:57:51

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

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

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

    // Etc.
}

我想在使用Moq Framework的单元测试方法中模拟它。我试着像这样嘲笑它:

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

但是这会在我的单元测试用例中引发异常。任何人都可以帮助我吗?

3 个答案:

答案 0 :(得分:132)

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

// 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();

答案 1 :(得分:14)

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

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对象是测试模型有效性的对象。这样我就可以设置对象的值并进行测试。

答案 2 :(得分:2)

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

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

JsonValueProviderFactory来到这里救援。只要您指定内容类型DefaultModelBinder“并将序列化的JSON对象传递到请求的输入流中,它就可以由"application/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;
}