TryUpdateModel, вызывающий ошибку в модульных тестах (Asp.net mvc) - PullRequest
1 голос
/ 15 июня 2011

у меня на пост действие в контроллере.Код приведен ниже

     [HttpPost]
    public ActionResult Create(Int64 id, FormCollection collection)
    {
        var data = Helper.CreateEmptyApplicationsModel();

        if (TryUpdateModel(data))
        {
              // TODO: Save data
             return RedirectToAction("Edit", "Applications", new { area = "Applications", id = id });
        }
        else
        {
            // TODO: update of the model has failed, look at the error and pass back to the view
            if (!ModelState.IsValid)
            {
                if (id != 0) Helper.ShowLeftColumn(data, id);
                return View("Create", data);
            }
        }

        return RedirectToAction("Details", "Info", new { area = "Deals", InfoId= id });

    }

Я написал тестовый пример для этого, как показано ниже

    [TestMethod]
    public void CreateTest_for_post_data()
    {           
        var collection = GetApplicantDataOnly();           
        _controller.ValueProvider = collection.ToValueProvider();
        var actual = _controller.Create(0, collection);
        Assert.IsInstanceOfType(actual, typeof(RedirectToRouteResult));
    }

Когда я отлаживаю этот одиночный тестовый пример, тестовый пример пройден, потому что условие if (TryUpdateModel(data)) возвращает true и оно переходит в условие if.Но когда я отлаживаю тестовые случаи из всего решения, этот тестовый тест не удался, потому что он переходит в условие else " if (TryUpdateModel (data))"".

Я не знаю, почему ..

Пожалуйста, помогите ...

Спасибо

Ответы [ 3 ]

1 голос
/ 15 июня 2011

Возможно, вы захотите немного очистить свой код:

[HttpPost]
public ActionResult Create(int id, FormCollection collection)
{
    var data = Helper.CreateEmptyApplicationsModel();

    if (!ModelState.IsValid)
    {
        if (id != 0)
        {
            Helper.ShowLeftColumn(data, id);
        }

        return View("Create", data);
    }

    if (TryUpdateModel(data))
    {
        return RedirectToAction("Edit", "Applications", new { area = "Applications", id = id });
    }

    return RedirectToAction("Details", "Info", new { area = "Deals", InfoId= id });
}

Не используйте Int64, просто используйте int.

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

Вам нужно как-то подделать / макетировать TryUpdateModel, чтобы он на самом деле не работал должным образом. Вместо этого вы «притворяетесь», чтобы вернуть истину. Вот несколько ссылок, чтобы помочь:

Как выполнить модульные тестовые действия без Mocking, которые используют UpdateModel?

Приведенный выше SO-ответ показывает пример использования RhinoMocks, который является бесплатной фальшивой структурой.

Или это:

http://www.codecapers.com/post/ASPNET-MVC-Unit-Testing-UpdateModel-and-TryUpdateModel.aspx

1 голос
/ 15 июня 2011

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

Я не использовал TryUpdateModel с того дня, как узнал о функции автоматической привязки. Автоматическое связывание, в двух словах, в значительной степени делает работу, которую TryUpdateModel сделает для вас, то есть установит объект модели в соответствии со значениями, найденными в FormCollection, а также попытается проверить модель. И делает это автоматически. Все, что вам нужно сделать, это поместить параметр в ActionMethod, и его свойства будут автоматически заполнены значениями, найденными в FormCollection. Ваша подпись в действии станет следующей:

public ActionResult Create(Int64 id, SomeModel data)

Теперь вам вообще не нужно звонить TryUpdateModel. Вам все еще нужно проверить, является ли ModelState действительным, чтобы решить, следует ли перенаправить или вернуть представление.

[HttpPost]
public ActionResult Create(Int64 id, SomeModel data)
{
    if (ModelState.IsValid)
    {
          // TODO: Save data
         return RedirectToAction("Edit", "Applications", new { area = "Applications", id = id });
    }
    else
    {
        if (id != 0) Helper.ShowLeftColumn(data, id);
        return View("Create", data);
    }
}

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

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

Решение: звоните TryValidateModel вместо ModelState.IsValid. Это заставит ваш юнит тест для проверки модели. Одна из проблем этого подхода заключается в том, что модель будет проверена один раз в ваших модульных тестах и ​​дважды в вашем приложении. Это означает, что любые обнаруженные ошибки будут дважды записаны в вашем приложении. Это означает, что если вы используете вспомогательный метод ValidationSummary в своем представлении, вы увидите несколько дублированных сообщений в списке.

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

protected void ClearModelStateErrors()
{
    foreach (var modelState in ModelState.Values)
        modelState.Errors.Clear();
}

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

Окончательное решение:

[HttpPost]
public ActionResult Create(Int64 id, SomeModel data)
{
    ClearModelStateErrors();

    if (ModelState.IsValid)
    {
          // TODO: Save data
         return RedirectToAction("Edit", "Applications", new { area = "Applications", id = id });
    }
    else
    {
        if (id != 0) Helper.ShowLeftColumn(data, id);
        return View("Create", data);
    }
}

ПРИМЕЧАНИЕ. Я понимаю, что не пролил свет на проблему с корнями. Это потому, что я не совсем понимаю корень проблемы. Если вы заметили сбой модульного теста, он завершается неудачей, потому что ArgumentNullException был сгенерирован, потому что ControllerContext равен нулю, и он передается методу, который выдает исключение, если ControllerContext равен нулю. (Прокляните команду MVC за их проклятое оборонительное программирование).

Если вы попытаетесь высмеять ControllerContext, вы все равно получите исключение, на этот раз NullReferenceException. Как ни странно, трассировка стека для исключения показывает, что оба исключения происходят в одном и том же методе, или, я бы сказал, конструкторе, расположенном в System.Web.Mvc.ChildActionValueProvider. У меня нет удобной копии исходного кода, поэтому я понятия не имею, что является причиной исключения, и я пока не нашел лучшего решения, чем предложенное выше. Мне лично не нравится мое решение, потому что я меняю способ кодирования своего приложения в пользу своих модульных тестов, но лучшей альтернативы, похоже, нет. Могу поспорить, что реальное решение будет включать насмешку над другим объектом, но я просто не знаю, что.

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

0 голосов
/ 15 июня 2011

Отладка ваших тестов и проверка коллекции ошибок состояния модели, все ошибки, с которыми столкнулась tryupdatemodel, должны быть там.

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