84 votes

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

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

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

    // Etc.
}
 

Je veux me moquer de 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?

137voto

Darin Dimitrov Points 528142

Vous n'avez pas besoin de vous moquer de ça. 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();
 

13voto

uadrive Points 909

Le seul problème que j'ai avec la solution ci-dessus est qu'il ne teste pas 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.

2voto

Rob Lyndon Points 2423

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

C'est très bien -- serialise votre modèle en tant que NameValueCollection, et de passer ensuite que dans l' NameValueCollectionValueProvider constructeur. Eh bien, pas tout à fait. Malheureusement, il ne fonctionne pas dans mon cas car mon modèle contient une collection, et l' NameValueCollectionValueProvider ne joue pas bien avec les collections.

L' JsonValueProviderFactory vient à la rescousse ici, cependant. Il peut être utilisé par l' DefaultModelBinder aussi longtemps que vous spécifiez un type de contenu de "application/json" et passer votre sérialisés objet JSON dans votre demande du flux d'entrée (Veuillez noter, parce que ce flux d'entrée est un flux de mémoire, c'est OK pour laisser undisposed, comme un flux de mémoire ne s'accroche pas à toutes les ressources externes):

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

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X