C# 如何使用Moq框架模拟ModelState.IsValid?
我正在检查我的控制器操作方法中的C# 如何使用Moq框架模拟ModelState.IsValid?,c#,asp.net-mvc,unit-testing,mocking,moq,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.S
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);
var modelState=new Mock();
Setup(m=>m.IsValid).返回(true);
但这在我的单元测试用例中引发了一个异常。有人能帮我吗?你不必嘲笑它。如果已经有控制器,则可以在初始化测试时添加模型状态错误:
// 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();
上述解决方案的唯一问题是,如果我设置属性,它实际上不会测试模型。我这样设置我的控制器
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对象是测试模型有效性的对象。通过这种方式,我可以设置对象的值并对其进行测试。uadrive的回答让我了解了一部分,但仍然存在一些差距。如果
新名称ValueCollectionValueProvider()
的输入中没有任何数据,则模型绑定器将控制器绑定到一个空模型,而不是模型
对象
这很好——只需将模型序列化为NameValueCollection
,然后将其传递给NameValueCollectionValueProvider
构造函数。嗯,不完全是。不幸的是,它在我的例子中不起作用,因为我的模型包含一个集合,NameValueCollectionValueProvider
不能很好地处理集合
然而,JsonValueProviderFactory
在这里起到了解救作用。只要您指定内容类型为“application/json
”,并将序列化的json对象传递到请求的输入流中(请注意,因为此输入流是内存流,所以可以将其保持不变,因为内存流不会保留任何外部资源),那么DefaultModelBinder
就可以使用它:
受保护的void BindModel(控制器控制器,TModel viewModel)
{
var controllerContext=SetUpControllerContext(控制器,视图模型);
var bindingContext=新模型bindingContext
{
ModelMetadata=ModelMetadataProviders.Current.GetMetadataForType(()=>viewModel,typeof(TModel)),
ValueProvider=new JsonValueProviderFactory().GetValueProvider(controllerContext)
};
新的DefaultModelBinder().BindModel(controller.ControllerContext,bindingContext);
controller.ModelState.Clear();
controller.ModelState.Merge(bindingContext.ModelState);
}
专用静态控制器上下文设置控制器上下文(控制器控制器,TModel viewModel)
{
var controllerContext=A.Fake();
controller.ControllerContext=ControllerContext;
var json=new JavaScriptSerializer().Serialize(viewModel);
A.CallTo(()=>controllerContext.Controller).返回(Controller);
A.CallTo(()=>controllerContext.HttpContext.Request.InputStream).Returns(newMemoryStream(Encoding.UTF8.GetBytes(json));
A.CallTo(()=>controllerContext.HttpContext.Request.ContentType).Returns(“application/json”);
返回控制器上下文;
}
非常好,这正是我想要的。我不知道有多少人在这样一个老问题上发帖,但它对我来说很有价值。谢谢。似乎是一个很好的解决方案,还在2016年:)用这样的东西单独测试模型不是更好吗?虽然这是一个聪明的解决方案,但我同意@RubberDuck。对于实际的独立单元测试,模型的验证应该是它自己的测试,而控制器的测试应该有它自己的测试。如果模型更改为违反ModelBinder验证,则控制器测试将失败,这是一个误报,因为控制器逻辑没有中断。要测试无效的ModelStateDictionary,只需为ModelState.IsValid检查添加一个虚假的ModelState错误以失败。我们如何设置ModelState.IsValid以达到真实情况?ModelState没有setter,因此我们无法执行以下操作:\ u controllerUnderTest.ModelState.IsValid=true。没有这一点,它就不会击中目标employee@Newton,默认情况下是正确的。您不需要指定任何内容来符合真实情况。如果你想找到错误的情况,你只需添加一个modelstate错误,如我的答案所示。通过这种方式,您可以获得更真实的控制器行为,您应该将模型验证交付给它的destiny属性验证。下面的帖子描述了这个()
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;
}