当发生意外错误时,我正在尝试验证我的 ASP.Net MVC 应用程序的行为。具体来说,我正在尝试验证用户是否被定向到我为我的应用程序定义的错误页面。我遇到的问题是我无法按预期验证控制器方法的行为。
对于我的正常行为测试,我创建了一个模拟业务规则对象并将其传递给我的控制器,然后从我要测试的控制器方法中验证 ViewResult。当事情按预期工作时,这对我的目的来说很好。但是,当我从业务规则方法抛出异常时,异常是通过控制器方法结果进行的,而不是由控制器处理(控制器方法具有“HandleError”属性),以便为我的错误页面提供适当的 ViewResult被退回。
有没有办法以这种方式验证 HandleError 属性的行为?还是我完全错了?我意识到我可以使用 Selenium(会访问实际服务器的浏览器内测试)来验证实际浏览器中的行为,但是模拟这些测试让我可以更快地完成这项工作,而且开销要少得多......
示例测试代码:
// WidgetController.Index() makes a call to GetWidgets to retrieve a
// List<Widget> instance.
// this works as expected since the appropriate ViewResult is returned
// by the controller
public void TestWidgetControllerIndex_NoResultsFound()
{
var mockBR = new Mock<IBusinessRules> { CallBase = true };
mockBR.Setup(br=>fr.GetWidgets()).Returns(new List<Widget>());
WidgetController controller = new WidgetController(mockBR.Object);
ViewResult result = (ViewResult)controller.Index();
Assert.AreEqual("Index", result.ViewName);
Assert.AreEqual(0,
((WidgetIndexViewData)result.ViewData.Model).Widgets.Count);
}
// this test is unable to reach the assertion statements due to the problem
// outlined above. WidgetController.Index has the HandleError attribute
// properly applied and the behaviour via the interface is as expected
public void TestWidgetControllerIndex_BusinessRulesExceptionEncountered()
{
var mockBR = new Mock<IBusinessRules> { CallBase = true };
mockBR.Setup(br=>fr.GetWidgets()).Throws<ApplicationException>();
WidgetController controller = new WidgetController(mockBR.Object);
ViewResult result = (ViewResult)controller.Index();
// The ApplicationException thrown by the business rules object bubbles
// up to the test through the line above. I was expecting this to be
// caught and handled by the HandleError filter (which would then let
// me verify the behaviour results via the assertion below)..
Assert.AreEqual("Error", result.ViewName);
}
我很感激任何关于我可能做错了什么的建议,或者我是否只是从完全错误的方向来解决这个问题。我假设在控制器方法级别进行测试是合适的方式,因为这是应用 HandleError 属性的地方。(如果我确实需要在应用程序级别进行测试,是否可以通过类似的方式进行测试实例化对象而不是使用像 Selenium 这样的东西?)
更新 我得出的结论是,我不应该在每个控制器操作上测试与 HandleError 属性相关的功能。我实际上并不关心它的作用,我只是想确保错误得到处理(从我的测试角度来看,它的自定义代码或 MVC 库是否没有区别,这是我想要验证的功能)。
我最终所做的是将我的控制器操作包装在 try/catch 块中,以强制错误视图作为方法的结果返回(而不是 ErrorHandler 属性在离开方法时捕获错误)。这样我就可以在我的单元测试中断言错误已通过适当的反馈得到正确处理。我对这给我的控制器方法增加的额外长度不太满意,但它确实让我向用户提供了友好的、具体的错误消息(我正在使用扩展方法来显示反馈并执行日志记录)。(所以肯定有 try/catch 方法的优点和缺点..)
我不是 100% 肯定这是最干净的方法,但它实现了我的目标,即能够验证错误是否通过控制器单元测试(快速)而不是必须在浏览器中执行测试(慢) . 所以基本上它现在已经足够好了,直到我能找到一个更清洁的解决方案。如果有人遇到类似问题并找到更好的解决方案,我决定提供赏金。