2010-03-13 62 views
20

我有一个实体实现了简单的添加操作和重定向到详细页面控制器:单位在ASP.NET MVC 2测试控制器RedirectToAction

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

这个伟大的工程(使用从RedirectToAction MvcContrib程序集)。

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

测试有:

var result = controller.Add(thing); 

但是导致这里是类型:System.Web.Mvc.RedirectToRouteResult(这是一个System.Web.Mvc.ActionResult)。它尚未执行Details方法。

我已经试过在返回的对象上调用ExecuteResult传递模拟ControllerContext,但是框架并不满意模拟对象中缺少细节。

我可以尝试填写详细信息等等,但然后我的测试代码比我测试的代码长,我觉得我需要为单元测试进行单元测试!

我在测试理念中错过了什么吗?当我无法恢复到返回状态时,如何测试此操作?

回答

7

你似乎为单元测试做得太多了。验证和数据访问通常由您从控制器操作调用的服务完成。你嘲笑这些服务,只测试他们被正确调用。

这样的事情(使用近似的语法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")); 
} 
+0

谢谢 - 这绝对有助于避免测试过程中框架的困难。所以,遵循这个习惯用法,我会对DBService进行单元测试,证明我可以添加东西,并为控制器进行单元测试,以证明其调用服务上的Save。但是我没有真正证明传入控制器的东西最终在数据库中。也许我可以用一堆更复杂的嘲讽规则来做到这一点......但是这并不正确,这是一个简单操作的很多测试锅炉板。 – 2010-03-13 23:16:42

+0

那么,确定测试的范围和努力以及测试什么等等,都是我与之奋斗的事情。我认为你应该尝试将你的测试分为两类:单元测试和集成测试。单元测试应该只测试非常小的功能单元,比如上面的测试。集成测试应该考虑如何整合所有内容,也许涵盖您拥有的小用户故事。我宁愿将集成测试尽可能接近“真实”使用,例如运行WatiN并实际单击一些链接。根本不需要嘲笑。 – rmac 2010-03-13 23:43:06

+0

如果你测试你的DBService,那你证明它是有效的。然后,你应该假设它可以并且它会正确处理数据库调用。所以如果你的控制器使用这个服务,你知道它会起作用。使用模拟框架,您可以验证传递给服务方法的参数,这就足够了。我认为rmacfie是对的,你可能试图做更深入的测试。 你的单元测试应该只包含一个动作,而不是一个完整的过程。 – 2010-03-14 23:53:29

12

有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对象充满来自数据库的正确数据。

31

我现在使用的是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分钟。

+2

太棒了!您可以用类似的方法检查其他路线值。对于RedirectToAction(“Details”,“Person”,{personId = 123})''你可以检查'personId':'Assert.AreEqual(123,actionResult.RouteValues [“personId”])' – Kirill 2012-12-21 05:08:49

6

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

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" 
     } 
    ); 
} 
+3

+1由于某种原因,MVC3控制器名称为空 – 2011-11-05 23:14:47