Как смоделировать ModelState.IsValid с использованием инфраструктуры Moq? - PullRequest
82 голосов
/ 29 сентября 2010

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

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

    // Etc.
}

Я хочу смоделировать это в моем методе модульного тестирования с использованием Moq Framework. Я пытался издеваться так:

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

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

Ответы [ 3 ]

134 голосов
/ 29 сентября 2010

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

// 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();
13 голосов
/ 04 июля 2012

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

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

2 голосов
/ 21 мая 2014

Ответ Уадрива взял меня с пути, но все еще были некоторые пробелы.Без каких-либо данных на входе 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;
}
...