Связывание моделей TDD и MVC - PullRequest
1 голос
/ 07 октября 2010

Допустим, у меня есть этот модульный тест:

    [Test]
    public void LastNameShouldNotBeEmpty()
    {
        ExampleController controller = new ExampleController();

        Person editedPerson = new Person { FirstName = "j", LastName = "" };
        controller.EditPerson(editedPerson);

        Assert.AreEqual(controller.ModelState.IsValid, false);
    }

И этот код:

public class ExampleController : Controller
{
    public ActionResult EditPerson(int personId)
    {
        // Serve up a view, whatever
        return View(Person.LoadPerson(personId));
    }

    [HttpPost]
    public ActionResult EditPerson(Person person)
    {
        if (ModelState.IsValid)
        {
            // TODO - actually save the modified person, whatever
        }

        return View(person);
    }
}

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

Меня беспокоит, что если я TDD выдвинула требование, что LastName не может бытьпусто, я не могу выполнить тест с использованием атрибутов DataAnnotation ([Обязательный] до объявления LastName для Person), потому что, когда метод действия контроллера вызывается из модульного теста, инфраструктура MVC не имеет возможности применить проверкуэто происходит во время привязки модели.

(Если я вручную выполнил проверку в методе контроллера EditPerson и добавил ошибку в ModelState, это можно было бы проверить из модульного теста.)

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

Я надеюсь, что цель моего вопроса ясна;Есть ли способ принудительно выполнить привязку истинной модели (включая ее поведение проверки, чтобы проверить, что я не забыл важные атрибуты проверки) из автоматического модульного теста?

Джефф

Ответы [ 5 ]

7 голосов
/ 08 октября 2010

Вот одно решение, которое я придумала.Требуется, чтобы в модульный тест была добавлена ​​одна строка кода, но я обнаружил, что мне не важно, принудительно ли выполняется проверка с помощью атрибутов для пользовательского кода в методе действия, что похоже на то, что тест больше соответствует духу.уточнения результатов, а не реализации.Это позволяет тесту пройти как написано, даже если проверка происходит из аннотаций данных.Обратите внимание, что новая строка находится прямо над вызовом метода действия EditPerson:

    [Test]
    public void LastNameShouldNotBeEmpty()
    {
        FakeExampleController controller = new FakeExampleController();

        Person editedPerson = new Person { FirstName = "j", LastName = "" };

        // Performs the same attribute-based validation that model binding would perform
        controller.ValidateModel(editedPerson);

        controller.EditPerson(editedPerson);

        Assert.AreEqual(false, controller.ModelState.IsValid);
        Assert.AreEqual(true, controller.ModelState.Keys.Contains("LastName"));
        Assert.AreEqual("Last name cannot be blank", controller.ModelState["LastName"].Errors[0].ErrorMessage);
    }

ValidateModel - фактически созданный мною метод расширения (контроллер имеет метод ValidateModel, но он защищен, поэтому он не можетвызываться из модульного теста напрямую).Он использует рефлексию для вызова защищенного метода TryValidateModel () на контроллере, который вызовет проверки на основе аннотаций, как если бы метод действия действительно вызывался через инфраструктуру MVC.NET.

public static class Extensions
{
    public static void ValidateModel<T>(this Controller controller, T modelObject)
    {
        if (controller.ControllerContext == null)
            controller.ControllerContext = new ControllerContext();

        Type type = controller.GetType();
        MethodInfo tryValidateModelMethod =
            type.GetMethods(BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance).Where(
                mi => mi.Name == "TryValidateModel" && mi.GetParameters().Count() == 1).First();

        tryValidateModelMethod.Invoke(controller, new object[] {modelObject});
    }
}

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

Джефф

1 голос
/ 07 октября 2010

Я согласен, что это не очень удовлетворительная ситуация. Однако есть несколько простых обходных путей:

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

  2. Создайте свой собственный валидатор, который отражает тип параметра viewmodel и проверяет его. Используйте его для проверки в ваших модульных тестах правильных атрибутов проверки. Предполагая, что ваш класс валидации не содержит ошибок, он должен быть эквивалентен алгоритму валидации в ASP.NET MVC ModelBinder. Я написал такой класс валидатора для другой цели, и он не намного сложнее, чем первый вариант.

0 голосов
/ 17 августа 2011

Мы можем использовать вспомогательный класс Validator для выполнения TDD с проверкой модели.Вы можете найти подробный блог о проверке модели тест-драйва здесь .

0 голосов
/ 08 октября 2010

Мне не нравятся тесты, которые лично проверяют наличие атрибутов, они делают тесты менее похожими на документацию и тесно связаны с моим пониманием ASP.NET MVC (что может быть неверно) и не тесно связаны с бизнесом.требования (о которых я забочусь).

Так что для такого рода вещей я заканчиваю интеграционными тестами, генерирую HTTP-запросы напрямую или через браузер с WatiN.Как только вы это сделаете, вы можете написать тесты без дополнительной абстракции MVC, тесты документируют то, что вы действительно заботитесь о том, чтобы быть правдой.Тем не менее, такие тесты медленны.

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

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

0 голосов
/ 08 октября 2010

Лично я считаю, что у вас должны быть модульные тесты, которые проверяют сами атрибуты, выходящие за рамки MVC. Это должно быть частью ваших модельных тестов, а не тестов вашего контроллера. Вы не написали код проверки MVC, поэтому не пытайтесь его протестировать! Просто проверьте тот факт, что ваш объект имеет правильные атрибуты, которые вы ожидаете.

Это очень грубо, но вы поняли ...

[Test]
public void LastNameShouldBeRequired()
{
    var personType = typeof(Person);
    var lastNamePropInfo = objType.GetProperty("LastName");
    var requiredAttrs = lastNamePropInfo.GetCustomAttributes(typeof(RequiredAttribute), true).OfType<RequiredAttribute>();
    Assert.IsTrue(requiredAttrs.Any());
}

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...