我正在尝试在发生意外错误时验证我的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块中,以强制由于方法而返回Error视图(而不是ErrorHandler属性在它离开方法时捕获错误)。这样我就能在单元测试中断言错误是通过适当的反馈正确处理的。我对我添加到控制器方法的额外长度不是很满意,但它确实让我向用户提供了一个友好的,特定的错误消息(我使用扩展方法来显示反馈并执行日志记录)。 (因此,确保尝试/捕获方法有利有弊。)
我不是100%肯定这是最干净的方法,但它实现了我的目标,即能够通过控制器单元测试(快速)验证错误,而不必在浏览器中执行测试(慢)。所以基本上它现在足够好,直到我找到一个更清洁的解决方案。如果有人遇到类似的问题并找到了更好的解决方案,我决定提供赏金。
答案 0 :(得分:7)
我认为你无法对此进行单元测试。你也不想。您可以测试控制器方法引发的预期异常。使用反射,您可以测试操作或控制器是否具有您期望它具有的属性,以及该属性是否具有某些预期的属性值。但是,拦截异常和执行属性的工作是框架的行为,而不是代码。一般来说,你不应该测试不属于你的代码(框架)。
答案 1 :(得分:1)
是的,我同意蒂姆的观点。您所描述的是集成测试。 HandleErrorAttribute的行为取决于底层的ASP.NET行为,例如您是否在web.config中打开或关闭自定义错误。
对于单元测试,您只需使用反射来验证属性是否存在 MVC团队对HandleErrorAttribute进行了单元测试,因此您不需要编写它们。 :)然后只需手动测试您的操作,以确保您想要的行为。只要您永远不会更改/删除属性,行为就不会改变。
如果您想自动化集成测试,可以使用Watin或Selenium自动执行实际的浏览器请求,以确保您的应用行为不会发生变化。