【问题标题】:Unit testing a controller in ASP.NET MVC 2 with RedirectToAction使用 RedirectToAction 在 ASP.NET MVC 2 中对控制器进行单元测试
【发布时间】:2010-03-13 22:00:35
【问题描述】:

我有一个控制器,它在实体上实现简单的添加操作并重定向到详细信息页面:

[HttpPost]
public ActionResult Add(Thing thing)
{ 
    // ... do validation, db stuff ...
    return this.RedirectToAction<c => c.Details(thing.Id));
}

这很好用(使用 MvcContrib 程序集中的 RedirectToAction)。

当我对这个方法进行单元测试时,我想访问从 Details 操作返回的 ViewData(这样我就可以获取新插入的东西的主键并证明它现在在数据库中)。

测试有:

var result = controller.Add(thing);

但这里的结果类型为:System.Web.Mvc.RedirectToRouteResult(即System.Web.Mvc.ActionResult)。它还没有执行 Details 方法。

我尝试在返回的对象上调用 ExecuteResult,传入模拟的 ControllerContext,但框架对模拟对象中缺少细节感到不满意。

我可以尝试填写详细信息等,但是我的测试代码比我正在测试的代码要长得多,我觉得我需要对单元测试进行单元测试!

我是否在测试理念中遗漏了什么?当我无法返回其返回状态时,如何测试此操作?

【问题讨论】:

    标签: asp.net-mvc unit-testing


    【解决方案1】:

    我目前正在使用 MVC2 RC2,来自 rmacfie 的回答对我来说不太奏效,但确实让我走上了正轨。

    我在测试中设法做到了这一点,无论是对是错:

    var actionResult = (RedirectToRouteResult)logonController.ForgotUsername(model);
    
    actionResult.RouteValues["action"].should_be_equal_to("Index");
    actionResult.RouteValues["controller"].should_be_equal_to("Logon");
    

    不确定这是否会对某人有所帮助,但可能会为您节省 10 分钟。

    【讨论】:

    • 太棒了!您可以以类似的方式检查其他路线值。对于RedirectToAction("Details", "Person", {personId=123}),您可以查看personIdAssert.AreEqual(123, actionResult.RouteValues["personId"])
    【解决方案2】:

    MVC Contrib TestHelper 非常适合测试大部分 ActionResult

    你可以在这里得到它: http://mvccontrib.codeplex.com/wikipage?title=TestHelper

    这是一个语法示例:

    var controller = new TestController();
    
    controller.Add(thing)
              .AssertActionRedirect()
              .ToAction<TestController>(x => x.Index());
    

    要测试数据是否已成功持久化,您可能应该直接询问您的数据库,我不知道您是否使用 ORM 或其他东西,但您应该做一些事情来获取数据库中最后一个插入的项目,然后与您提供给 Add ActionResult 的值进行比较,看看是否可以。

    我不认为测试您的 Details ActionResult 以查看您的数据是否持久化是正确的方法。那不是单元测试,而是功能测试。

    但您还应该对您的 Details 方法进行单元测试,以确保您的 viewdata 对象填充了来自数据库的正确数据。

    【讨论】:

      【解决方案3】:

      您似乎为单元测试做的太多了。验证和数据访问通常由您从控制器操作调用的服务完成。您模拟这些服务并仅测试它们是否被正确调用。

      类似这样的东西(使用 Rhino.Mocks 和 NUnit 的近似语法):

      [Test]
      public void Add_SavesThingToDB()
      {
          var dbMock = MockRepository.GenerateMock<DBService>();
          dbMock.Expect(x => x.Save(thing)).Repeat.Once();
      
          var controller = new MyController(dbMock);
          controller.Add(new Thing());
      
          dbMock.VerifyAllExpectations();
      }
      
      [Test]
      public void Add_RedirectsAfterSave()
      {
          var dbMock = MockRepository.GenerateMock<DBService>();
      
          var controller = new MyController(dbMock);
          var result = (RedirectToRouteResult)controller.Add(new Thing());
      
          Assert.That(result.Url, Is.EqualTo("/mynew/url"));
      }
      

      【讨论】:

      • 谢谢——这绝对有助于避免测试期间框架的困难。因此,按照这个习惯用法,我将对 DBService 进行单元测试,证明我可以添加东西,并对控制器进行单元测试,证明它在服务上调用 Save。但是我还没有真正证明传递给控制器​​的东西最终会进入数据库。也许我可以用一大堆更复杂的模拟规则来做到这一点......但这感觉不对,这是一个简单操作的大量测试样板。
      • 嗯,确定测试的范围和工作量以及测试的内容等,也是我在努力解决的问题。我认为您应该尝试将测试分为两类:单元测试和集成测试。单元测试应该只测试非常小的功能单元,例如上面的测试。集成测试应该着眼于一切是如何集成的,可能会涵盖你拥有的一个小用户故事。我希望使集成测试尽可能接近“真实”使用,例如运行 WatiN 并实际单击几个链接。根本不需要嘲笑那里。
      • 如果你测试你的 DBService,你证明它是有效的。然后,您应该假设它可以并且它将正确处理数据库调用。因此,如果您的控制器使用此服务,您就知道它会正常工作。使用模拟框架,您可以验证传递给服务方法的参数,这就足够了。我认为 rmacfie 是对的,您可能正试图进行更深入的测试。您的单元测试应该只涵盖一个动作,而不是整个过程。
      【解决方案4】:

      我有一个测试重定向的静态辅助方法。

      public static class UnitTestHelpers
      {
          public static void ShouldEqual<T>(this T actualValue, T expectedValue)
          {
              Assert.AreEqual(expectedValue, actualValue);
          }
      
          public static void ShouldBeRedirectionTo(this ActionResult actionResult, object expectedRouteValues)
          {
              RouteValueDictionary actualValues = ((RedirectToRouteResult)actionResult).RouteValues;
              var expectedValues = new RouteValueDictionary(expectedRouteValues);
      
              foreach (string key in expectedValues.Keys)
              {
                  Assert.AreEqual(expectedValues[key], actualValues[key]);
              }
          }
      }
      

      那么创建重定向测试就很简单了。

      [Test]
      public void ResirectionTest()
      {
          var result = controller.Action();
      
          result.ShouldBeRedirectionTo(
              new
              {
                  controller = "ControllerName",
                  action = "Index"
              }
          );
      }
      

      【讨论】:

      • +1 表示扩展方法,但由于某种原因 MVC3 控制器名称为空
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-02-08
      • 1970-01-01
      相关资源
      最近更新 更多