Модульные тесты на проверку MVC



Как я могу проверить, что мое действие контроллера помещает правильные ошибки в ModelState при проверке сущности, когда я использую проверку DataAnnotation в MVC 2 Preview 1?



какой-то код, чтобы проиллюстрировать. Во-первых, действие:



    [HttpPost]
public ActionResult Index(BlogPost b)
{
if(ModelState.IsValid)
{
_blogService.Insert(b);
return(View("Success", b));
}
return View(b);
}


и вот неудачный модульный тест, который, как я думаю, должен проходить, но не является (используя MbUnit & Moq):



[Test]
public void When_processing_invalid_post_HomeControllerModelState_should_have_at_least_one_error()
{
// arrange
var mockRepository = new Mock<IBlogPostSVC>();
var homeController = new HomeController(mockRepository.Object);

// act
var p = new BlogPost { Title = "test" }; // date and content should be required
homeController.Index(p);

// assert
Assert.IsTrue(!homeController.ModelState.IsValid);
}


Я думаю, в дополнение к этому вопросу, должны я тестирую проверку, и я должен тестировать его таким образом?

473   12  

12 ответов:

вместо передачи в BlogPost вы также можете объявить параметр actions как FormCollection. Затем вы можете создать BlogPost сами и зовите UpdateModel(model, formCollection.ToValueProvider());.

это вызовет проверку для любого поля в FormCollection.

    [HttpPost]
    public ActionResult Index(FormCollection form)
    {
        var b = new BlogPost();
        TryUpdateModel(model, form.ToValueProvider());

        if (ModelState.IsValid)
        {
            _blogService.Insert(b);
            return (View("Success", b));
        }
        return View(b);
    }

просто убедитесь, что ваш тест добавляет значение null для каждого поля формы, которые вы хотите оставить пустым.

я обнаружил, что делать это таким образом, за счет нескольких дополнительных строк кода, делает мои модульные тесты похожи способ вызова кода во время выполнения более тесно делает их более ценными. Также вы можете проверить, что происходит, когда кто-то вводит "abc" в элементе управления, привязанном к свойству int.

ненавижу некро старый пост, но я думал, что добавлю свои собственные мысли (так как у меня была эта проблема и я столкнулся с этим сообщением, ища ответ).

  1. не тестируйте проверку в тестах контроллера. Либо Вы доверяете проверке MVC, либо пишете свой собственный (т. е. не тестируете чужой код, тестируете свой код)
  2. если вы хотите проверить проверку делает то, что вы ожидаете, проверить его в тестах модели (я делаю это для нескольких моих более сложных регулярных выражений корректность.)

что вы действительно хотите проверить здесь, так это то, что ваш контроллер делает то, что вы ожидаете, когда проверка завершится неудачей. Это ваш код, и ваши ожидания. Тестирование это легко, как только вы понимаете, что это все, что вы хотите проверить:

[test]
public void TestInvalidPostBehavior()
{
    // arrange
    var mockRepository = new Mock<IBlogPostSVC>();
    var homeController = new HomeController(mockRepository.Object);
    var p = new BlogPost();

    homeController.ViewData.ModelState.AddModelError("Key", "ErrorMessage"); // Values of these two strings don't matter.  
    // What I'm doing is setting up the situation: my controller is receiving an invalid model.

    // act
    var result = (ViewResult) homeController.Index(p);

    // assert
    result.ForView("Index")
    Assert.That(result.ViewData.Model, Is.EqualTo(p));
}

У меня была та же проблема, и после прочтения ответа и комментария Паулса я искал способ ручной проверки модели представления.

нашел в этом уроке который объясняет, как вручную проверить ViewModel, который использует DataAnnotations. Они основных фрагмент кода находится в конце поста.

Я немного изменил код-в учебнике опущен 4-й параметр TryValidateObject (validateAllProperties). Для того, чтобы получить все аннотации для проверки, это должно быть установлено в true.

дополнительно я преобразовал код в общий метод, чтобы сделать тестирование проверки ViewModel простым:

    public static void ValidateViewModel<TViewModel, TController>(this TController controller, TViewModel viewModelToValidate) 
        where TController : ApiController
    {
        var validationContext = new ValidationContext(viewModelToValidate, null, null);
        var validationResults = new List<ValidationResult>();
        Validator.TryValidateObject(viewModelToValidate, validationContext, validationResults, true);
        foreach (var validationResult in validationResults)
        {
            controller.ModelState.AddModelError(validationResult.MemberNames.FirstOrDefault() ?? string.Empty, validationResult.ErrorMessage);
        }
    }

до сих пор это работало очень хорошо для нас.

при вызове homeController.Метод индекса в вашем тесте вы не используете ни одну из рамок MVC, которая запускает проверку so ModelState.IsValid всегда будет правдой. В нашем коде мы вызываем вспомогательный метод проверки непосредственно в контроллере, а не с помощью внешней проверки. У меня не было большого опыта работы с DataAnnotations (мы используем NHibernate.Валидаторы) может быть, кто-то еще может предложить руководство, как вызвать Validate из вашего контроллера.

Я исследовал это сегодня, и я нашел этот блог Роберто Эрнандес (MVP), который, кажется, обеспечивает лучшее решение для запуска валидаторов для действия контроллера во время модульного тестирования. Это приведет к исправлению ошибок в ModelState при проверке сущности.

Я использую ModelBinders в своих тестовых случаях, чтобы иметь возможность обновлять модель.Значение является допустимым.

var form = new FormCollection();
form.Add("Name", "0123456789012345678901234567890123456789");

var model = MvcModelBinder.BindModel<AddItemModel>(controller, form);

ViewResult result = (ViewResult)controller.Add(model);

С моим MvcModelBinder.Метод BindModel следующим образом (в основном используется тот же код внутренне в рамках MVC):

        public static TModel BindModel<TModel>(Controller controller, IValueProvider valueProvider) where TModel : class
        {
            IModelBinder binder = ModelBinders.Binders.GetBinder(typeof(TModel));
            ModelBindingContext bindingContext = new ModelBindingContext()
            {
                FallbackToEmptyPrefix = true,
                ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(null, typeof(TModel)),
                ModelName = "NotUsedButNotNull",
                ModelState = controller.ModelState,
                PropertyFilter = (name => { return true; }),
                ValueProvider = valueProvider
            };

            return (TModel)binder.BindModel(controller.ControllerContext, bindingContext);
        }

Это не совсем ответ на ваш вопрос, потому что он отказывается от DataAnnotations, но я добавлю его, потому что это может помочь другим людям писать тесты для своих контроллеров:

у вас есть возможность не использовать проверку представленной системы.ComponentModel.DataAnnotations, но все еще используя ViewData.Объект ModelState, используя его AddModelError метод и некоторые другие механизмы проверки. Например:

public ActionResult Create(CompetitionEntry competitionEntry)
{        
    if (competitionEntry.Email == null)
        ViewData.ModelState.AddModelError("CompetitionEntry.Email", "Please enter your e-mail");

    if (ModelState.IsValid)
    {
       // insert code to save data here...
       // ...

       return Redirect("/");
    }
    else
    {
        // return with errors
        var viewModel = new CompetitionEntryViewModel();
        // insert code to populate viewmodel here ...
        // ...


        return View(viewModel);
    }
}

Это все еще позволяет вам воспользоваться Html.ValidationMessageFor() вещи, которые MVC генерирует, не используя DataAnnotations. Вы должны убедиться, что ключ, который вы используете с AddModelError соответствует тому, что представление ожидает для сообщений проверки.

контроллер затем становится тестируемым, потому что проверка происходит явно, а не выполняется автоматически с помощью платформы MVC.

Я согласен, что ARM имеет лучший ответ: проверьте поведение вашего контроллера, а не встроенную проверку.

однако вы также можете проверить модуль, что ваша модель/ViewModel имеет правильные атрибуты проверки. Допустим, ваш ViewModel выглядит так:

public class PersonViewModel
{
    [Required]
    public string FirstName { get; set; }
}

этот модульный тест будет проверять наличие :

[TestMethod]
public void FirstName_should_be_required()
{
    var propertyInfo = typeof(PersonViewModel).GetProperty("FirstName");

    var attribute = propertyInfo.GetCustomAttributes(typeof(RequiredAttribute), false)
                                .FirstOrDefault();

    Assert.IsNotNull(attribute);
}

в отличие от ARM, у меня нет проблем с рытьем могил. Итак, вот мое предложение. Он основывается на ответе Джайлса Смита и работает для ASP.NET MVC4 (я знаю, что вопрос о MVC 2, но Google не различает при поиске ответов, и я не могу проверить на MVC2.) Вместо того, чтобы помещать код проверки в общий статический метод, я помещаю его в контроллер тестирования. Контроллер имеет все необходимое для проверки. Итак, контроллер тестирования выглядит так это:

using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Wbe.Mvc;

protected class TestController : Controller
    {
        public void TestValidateModel(object Model)
        {
            ValidationContext validationContext = new ValidationContext(Model, null, null);
            List<ValidationResult> validationResults = new List<ValidationResult>();
            Validator.TryValidateObject(Model, validationContext, validationResults, true);
            foreach (ValidationResult validationResult in validationResults)
            {
                this.ModelState.AddModelError(String.Join(", ", validationResult.MemberNames), validationResult.ErrorMessage);
            }
        }
    }

конечно, класс не должен быть защищенным внутренним классом, то есть так, как я использую его сейчас, но я, вероятно, собираюсь повторно использовать этот класс. Если где-то есть модель MyModel, которая украшена хорошими атрибутами аннотации данных, то тест выглядит примерно так:

    [TestMethod()]
    public void ValidationTest()
    {
        MyModel item = new MyModel();
        item.Description = "This is a unit test";
        item.LocationId = 1;

        TestController testController = new TestController();
        testController.TestValidateModel(item);

        Assert.IsTrue(testController.ModelState.IsValid, "A valid model is recognized.");
    }

преимущество этой установки заключается в том, что я могу повторно использовать контроллер тестирования для тестов всех моих моделей и, возможно, смогу расширить его, чтобы немного больше узнать о контроллере или используйте защищенные методы, которые есть у контроллера.

надеюсь, что это помогает.

Если вы заботитесь о проверке, но вы не заботитесь о том, как она реализуется, если вы заботитесь только о проверке вашего метода действия на самом высоком уровне абстракции, независимо от того, реализуется ли он с использованием DataAnnotations, ModelBinders или даже ActionFilterAttributes, то вы можете использовать Xania.сеть САШ.Симулятор nuget пакет следующим образом:

install-package Xania.AspNet.Simulator

--

var action = new BlogController()
    .Action(c => c.Index(new BlogPost()), "POST");
var modelState = action.ValidateRequest();

modelState.IsValid.Should().BeFalse();

на основе ответа и комментариев @giles-smith, для веб-API:

    public static void ValidateViewModel<TViewModel, TController>(this TController controller, TViewModel viewModelToValidate) 
        where TController : ApiController
    {
        var validationContext = new ValidationContext(viewModelToValidate, null, null);
        var validationResults = new List<ValidationResult>();
        Validator.TryValidateObject(viewModelToValidate, validationContext, validationResults, true);
        foreach (var validationResult in validationResults)
        {
            controller.ModelState.AddModelError(validationResult.MemberNames.FirstOrDefault() ?? string.Empty, validationResult.ErrorMessage);
        }
    }

смотрите на ответ edit выше...

ответ@giles-smith является моим предпочтительным подходом, но реализация может быть упрощена:

    public static void ValidateViewModel(this Controller controller, object viewModelToValidate)
    {
        var validationContext = new ValidationContext(viewModelToValidate, null, null);
        var validationResults = new List<ValidationResult>();
        Validator.TryValidateObject(viewModelToValidate, validationContext, validationResults, true);
        foreach (var validationResult in validationResults)
        {
            controller.ModelState.AddModelError(validationResult.MemberNames.FirstOrDefault() ?? string.Empty, validationResult.ErrorMessage);
        }
    }

Comments

    Ничего не найдено.