web-dev-qa-db-fra.com

Comment se moquer de ModelState.IsValid en utilisant le framework Moq?

Je vérifie ModelState.IsValid dans ma méthode d'action de contrôleur qui crée un employé comme ceci:

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

    // Etc.
}

Je veux le simuler dans ma méthode de test unitaire en utilisant Moq Framework. J'ai essayé de me moquer comme ça:

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

Mais cela jette une exception dans mon cas de test unitaire. Est-ce que quelqu'un pourrait m'aider?

83
Mazen

Vous n'avez pas besoin de vous moquer de lui. Si vous avez déjà un contrôleur, vous pouvez ajouter une erreur d'état du modèle lors de l'initialisation de votre 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();
135
Darin Dimitrov

Le seul problème que j'ai avec la solution ci-dessus est qu'elle ne teste pas réellement le modèle si je définis des attributs. J'ai configuré mon contrôleur de cette façon.

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

L'objet modelBinder est l'objet qui teste la validité du modèle. De cette façon, je peux simplement définir les valeurs de l'objet et le tester.

13
uadrive

la réponse de uadrive m'a pris une partie du chemin, mais il y avait encore des lacunes. Sans données dans l'entrée de new NameValueCollectionValueProvider(), le classeur de modèle liera le contrôleur à un modèle vide, pas à l'objet model.

C'est très bien - sérialisez simplement votre modèle en tant que NameValueCollection, puis passez-le dans le constructeur NameValueCollectionValueProvider. Enfin, pas tout à fait. Malheureusement, cela n'a pas fonctionné dans mon cas car mon modèle contient une collection et le NameValueCollectionValueProvider ne fonctionne pas bien avec les collections.

Le JsonValueProviderFactory vient à la rescousse ici, cependant. Il peut être utilisé par DefaultModelBinder tant que vous spécifiez un type de contenu "application/json "et passez votre objet JSON sérialisé dans le flux d'entrée de votre demande (veuillez noter, car ce flux d'entrée est un flux de mémoire, il est OK de le laisser non exposé, car un flux de mémoire ne conserve aucune ressource externe):

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;
}
2
Rob Lyndon