Как издеваться над ModelState.IsValid, используя фреймворк Moq?

Я проверяю ModelState.IsValid в моем методе действий контроллера, который создает Employee следующим образом:

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

    // Etc.
}

Я хочу высмеять это в моем методе unit test с помощью Moq Framework. Я попытался издеваться над этим следующим образом:

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

Но это исключает исключение в моем случае unit test. Может ли кто-нибудь помочь мне здесь?

Ответ 1

Вам не нужно издеваться над этим. Если у вас уже есть контроллер, вы можете добавить ошибку состояния модели при инициализации теста:

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

Ответ 2

Единственная проблема, с которой я сталкиваюсь в решении выше, заключается в том, что он не тестирует модель, если я устанавливаю атрибуты. Я настраиваю свой контроллер таким образом.

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 - это объект, который проверяет правильность модели. Таким образом, я могу просто установить значения объекта и протестировать его.

Ответ 3

Ответ uadrive взял меня на части, но все же были некоторые пробелы. Без каких-либо данных на входе new NameValueCollectionValueProvider(), связующее устройство моделирует привязку контроллера к пустой модели, а не к объекту model.

Это прекрасно - просто сериализуйте свою модель как NameValueCollection, а затем передайте ее в конструктор NameValueCollectionValueProvider. Ну, не совсем. К сожалению, в моем случае это не сработало, потому что моя модель содержит коллекцию, а NameValueCollectionValueProvider не играет хорошо с коллекциями.

Тем не менее, JsonValueProviderFactory приходит на помощь. Его можно использовать DefaultModelBinder, пока вы укажете тип содержимого "application/json "и передаете свой последовательный объект JSON в поток ввода запроса (обратите внимание, поскольку этот входной поток является потоком памяти, он в порядке, чтобы выйти он не отображается, так как поток памяти не держится за какие-либо внешние ресурсы):

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