TryUpdateModel导致单元测试用例出错(Asp.net mvc)

时间:2011-06-15 14:52:13

标签: asp.net-mvc-3 unit-testing

我在控制器中进行了后期操作。代码如下:

     [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条件。 但是当我从整个解决方案中调试测试用例时,这个测试用例失败了,因为它进入了“ if(TryUpdateModel(data))”的其他条件。

我不知道为什么......

请帮忙......

由于

3 个答案:

答案 0 :(得分:1)

您可能想稍微清理一下代码:

[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,以便它实际上不能正常运行。相反,你“伪造”它返回真实。以下是一些帮助链接:

How do I Unit Test Actions without Mocking that use UpdateModel?

上面的SO答案显示了一个使用RhinoMocks的例子,这是一个免费的模拟框架。

或者这个:

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

答案 1 :(得分:1)

如果您没有 需要 来使用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);
    }
}

这不会在单元测试中引发异常,因此解决了一个问题。但是,现在还有另一个问题。如果您使用上面的代码运行您的应用程序,它将工作得很好。您将在输入Action时验证模型,并返回正确的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将始终报告模型有效,即使它不是。

答案 2 :(得分:0)

调试测试并检查modelstate错误集合,tryupdatemodel遇到的所有错误都应该在那里。