值用于隔离地对控制器进行单元测试

本文关键字:单元测试 控制器 用于 隔离 | 更新日期: 2023-09-27 17:50:29

寻找asp.net web api 2中单元测试控制器的指导。我有一个标准的多层架构:控制器->服务层->数据访问层。

服务层可能抛出源自服务层的异常或来自数据访问层的异常[1]。因为每个控制器方法中的异常处理逻辑都是非常简单的:

public IHttpActionResult MyControllerAction()
{
    try
    {
    // do something and return IHttpActionResult
    }
    catch(Exception ex)
    {
    // map exceptions to specific IHttpActionResult with corresponding (status code and error (response body) formatting)
    return HandleException(ex);  
    }
}

我选择取消try/catch &处理每个控制器动作的逻辑,并将其合并到一个地方。以下一个可以:{IHttpActionInvoker, ExceptionFilter,或ExceptionHandler}。

不幸的是,因为异常处理逻辑现在不在控制器中,我的控制器单元测试不再在IHttpActionResult级别隔离。

以前我可以测试各种输入和预期输出,如:

  • 给定: input1, 期望IHttpActionResult: OK
  • 给定: input2, 期望IHttpActionResult: NotFound
  • 给定: input3, Expect: BadRequest
  • 给定: input4, 期望IHttpActionResult: PreconditionFailed

这在概念上感觉非常干净,我的控制器和单元测试都与众所周知的IHttpActionResults交互(不是服务层或更低的概念)。

现在我将异常处理提升到一个全局处理程序,控制器单元测试现在抛出无数的服务或数据访问层异常。当然,我可以通过集成测试实现之前的功能,例如ExceptionHandler + controller。

问题:在这种设置中,单独对控制器进行单元测试的价值是什么?基线值似乎是使用ExceptionHandler + controller进行集成测试,以至少验证从各种输入返回的所有状态代码。

[1]数据访问异常是通用的,与特定的数据访问实现无关。我没有看到将其封装在服务层异常中的值。

值用于隔离地对控制器进行单元测试

这并不能直接回答你的问题,但是异常处理集中化的另一种选择是使用接受lambda或闭包的函数式方法:

(如。(在基本控制器或控制器辅助器上)

protected IHttpActionResult InvokeWithExceptionHandler(Func<IHttpActionResult> tryBlock)
{
    try
    {
        return tryBlock();
    }
    catch (Exception ex)
    {
        // map exceptions to specific IHttpActionResult ... etc
        return HandleException(ex);
    }   
}

这将允许你的控制器方法只是"编码"快乐流用例的控制器,因此得到相同的DRY异常处理行为,但仍然允许相同的单元可测试性,因为你以前:

    public IHttpActionResult DoSomething(MyEntity myModel)
    {
        return InvokeWithExceptionHandler(
            () =>
            {
                // ... Do controller things here
                return View("MyView", myModel);
            });
    }

我猜如果有自定义的catch错误或finally块处理,您可以相应地重载Invoker

这里的好处是您不需要重复异常处理单元测试场景,并且可以将单元测试集中在正常情况下控制器流中的分支上。