c# - unit - ¿Cómo burlarse de ModelState.IsValid usando el framework Moq?
unit testing with mocks (3)
El único problema que tengo con la solución anterior es que en realidad no prueba el modelo si establezco atributos. Configuro mi controlador de esta manera.
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;
}
El objeto modelBinder es el objeto que prueba la validez del modelo. De esta forma puedo simplemente establecer los valores del objeto y probarlo.
Estoy comprobando ModelState.IsValid
en mi método de acción de controlador que crea un empleado como este:
[HttpPost]
public virtual ActionResult Create(EmployeeForm employeeForm)
{
if (this.ModelState.IsValid)
{
IEmployee employee = this._uiFactoryInstance.Map(employeeForm);
employee.Save();
}
// Etc.
}
Quiero simularlo en mi método de prueba unitaria usando Moq Framework. Traté de burlarlo así:
var modelState = new Mock<ModelStateDictionary>();
modelState.Setup(m => m.IsValid).Returns(true);
Pero esto arroja una excepción en mi caso de prueba de unidad. ¿Puede alguien ayudarme aquí?
La respuesta de uadrive me llevó a una parte del camino, pero todavía había algunos vacíos. Sin ningún dato en la entrada al new NameValueCollectionValueProvider()
, el new NameValueCollectionValueProvider()
modelos vinculará el controlador a un modelo vacío, no al objeto model
.
Eso está bien: simplemente serialice su modelo como NameValueCollection
, y luego páselo al constructor NameValueCollectionValueProvider
. Bueno, no del todo. Lamentablemente, no funcionó en mi caso porque mi modelo contiene una colección y el NameValueCollectionValueProvider
no funciona bien con las colecciones.
La JsonValueProviderFactory
viene al rescate aquí, sin embargo. Lo puede usar DefaultModelBinder
siempre que especifique un tipo de contenido de "application/json
" y pase su objeto JSON serializado a la secuencia de entrada de su solicitud (tenga en cuenta que esta secuencia de entrada es una secuencia de memoria, está bien dejarla) no presentado, ya que una secuencia de memoria no se aferra a ningún recurso externo):
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;
}
No necesitas burlarte de eso. Si ya tiene un controlador, puede agregar un error de estado del modelo al inicializar su prueba:
// 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();