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

发布于 2024-09-25 17:45:43 字数 543 浏览 4 评论 0原文

我正在控制器操作方法中检查 ModelState.IsValid ,该方法创建一个如下所示的 Employee :

[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);

但这在我的单元测试用例中引发了异常。有人可以帮我吗?

I'm checking ModelState.IsValid in my controller action method that creates an Employee like this:

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

    // Etc.
}

I want to mock it in my unit test method using Moq Framework. I tried to mock it like this:

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

But this throws an exception in my unit test case. Can anyone help me out here?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

潦草背影 2024-10-02 17:45:43

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

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

You don't need to mock it. If you already have a controller you can add a model state error when initializing your test:

// 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();
失眠症患者 2024-10-02 17:45:43

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

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

The only issue I have with the solution above is that it doesn't actually test the model if I set attributes. I setup my controller this way.

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;
    }

The modelBinder object is the object that test the validity of the model. This way I can just set the values of the object and test it.

初相遇 2024-10-02 17:45:43

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

没关系,只需将模型序列化为 NameValueCollection,然后将其传递到 NameValueCollectionValueProvider 构造函数即可。嗯,不完全是。不幸的是,它在我的例子中不起作用,因为我的模型包含一个集合,并且 NameValueCollectionValueProvider 与集合不能很好地配合。

不过,JsonValueProviderFactory 在这里发挥了作用。只要您指定内容类型“application/json”并将序列化的 JSON 对象传递到请求的输入流中,DefaultModelBinder 就可以使用它(请注意,因为这个输入流是一个内存流,所以可以不处理它,因为内存流不保留任何外部资源):

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;
}

uadrive's answer took me part of the way, but there were still some gaps. Without any data in the input to new NameValueCollectionValueProvider(), the model binder will bind the controller to an empty model, not to the model object.

That's fine -- just serialise your model as a NameValueCollection, and then pass that into the NameValueCollectionValueProvider constructor. Well, not quite. Unfortunately, it didn't work in my case because my model contains a collection, and the NameValueCollectionValueProvider does not play nicely with collections.

The JsonValueProviderFactory comes to the rescue here, though. It can be used by the DefaultModelBinder as long as you specify a content type of "application/json" and pass your serialised JSON object into your request's input stream (Please note, because this input stream is a memory stream, it's OK to leave it undisposed, as a memory stream doesn't hold on to any external resources):

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;
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文