如何为下面的Http调用添加一个异常测试,返回一个json

时间:2017-03-22 11:12:43

标签: c# asp.net-mvc unit-testing mstest

[HttpPost]
[Route("TnC")]
public IHttpActionResult TnC(CustomViewModel myViewModel)
{
    try
    {
        return Json(_Internal.TnC(myViewModel, LoggedInUser));
    }
    catch (BusinessException exception)
    {
        return Json(BuildErrorModelBase(exception));
    }
}

_Internal是一个服务,其保证99.99%的正常运行时间,并且未定义正式的故障合同接口。

我的应用程序级别(业务层级别)处理的异常 BusinessException - 根类

BusinessException 的定义如下

public class BusinessException : Exception
{
    BusinessException()...
    BusinessExceptionFoo()...
    BusinessExceptionBar()...
    //...
}

目前的测试方法是

待办事项:添加例外测试

[TestMethod]
[ExpectedException(typeof(BusinessException),
        "Not a valid Business Case")]
public void TnCTest()
{
    var bookingService = myContainer.Resolve<mySvc>();

    var controller = new LinkBookingController(mySvc, myContainer);
    var expectedResult = controller.TnC(new myViewModel
    {
        ...params
    });

    var actualResult = GetData<Result<myViewModel>>(expectedResult);

    Assert.AreEqual(expectedResult, actualResult);
}

expectedResult==actualResult不测试代码的异常块。 除了手动删除以太网电缆以获取此特定类型的服务器错误之外,如何构造使服务抛出异常的请求。

我能想到的最好的是

#if DEBUG && UnitTestExceptions
        throw new BusinessException();
#endif

但是必须有更好的选择。

2 个答案:

答案 0 :(得分:2)

测试方法有一些问题。

正在将应该重构的行为中的横切关注点混合到ExceptionHandler中。可能是那段代码在那个控制器中重复了很多次,而其他人则喜欢它(DRY)。

public class WebApiExceptionHandler : ExceptionHandler {

    public override void Handle(ExceptionHandlerContext context) {
        var innerException = context.ExceptionContext.Exception;
        // Ignore HTTP errors
        if (innerException.GetType().IsAssignableFrom(typeof(System.Web.HttpException))) {
            return;
        }

        if(innerException is BusinessException) {
            context.Result = BuildErrorResult(exception);
            return;
        }

        //...other handler code
    }

    IHttpActionResult BuildErrorResult(BusinessException exception) { 
        //... your logic here 
    }
}

以下扩展方法可用于在启动期间将处理程序添加到HttpConfiguration,这也假定应用程序正在利用依赖性反转服务。

public static HttpConfiguration ReplaceExceptionHandler(this HttpConfiguration config) {
    var errorHandler = config.Services.GetExceptionHandler();
    if (!(errorHandler is WebApiExceptionHandler)) {
        var service = config.Services.GetService(typeof(WebApiExceptionHandler));
        config.Services.Replace(typeof(IExceptionHandler), service);
    }
    return config;
}

现在已经处理了跨领域的问题,行动变得更简单,更容易测试。这是ApiController的简化示例

public class LinkBookingController : ApiController {
    private IBookingService bookingService;

    public LinkBookingController(IBookingService service) {
        bookingService = service;
    }

    [HttpPost]
    [Route("TnC")]
    public IHttpActionResult TnC(CustomViewModel myViewModel) {

        return Json(bookingService.TnC(myViewModel, User));

    }
}

其中IBookingService定义为

public interface IBookingService {
    BookingModel TnC(CustomViewModel viewModel, IPrincipal user);
}

使用像Moq这样的模拟框架,可以根据需要抛出异常。

[TestMethod]
[ExpectedException(typeof(BusinessException), "Not a valid Business Case")]
public void TnC_Should_Throw_BusinessException() {
    //Arrange
    var bookingService = new Mock<IBookingService>();

    var controller = new LinkBookingController(bookingService.Object);

    var viewModel  = new myViewModel
    {
        //...params
    };

    bookingService.Setup(_ => _.TnC(viewModel, It.IsAny<IPrincipal>())).Throws<BusinessException>()

    //Act
    var expectedResult = controller.TnC(viewModel);

    //Assert
    //...the ExpectedException attribute should assert if it was thrown
}

要测试如何处理异常,请对异常处理程序而不是控制器进行单元测试,因为这不是控制器的责任。

尽量保持控制器的精益,并专注于其UI问题。

答案 1 :(得分:1)

正如Nkosi在他的评论中提到的,你需要做的是为任何类型_Internal添加一个接口,这样你的控制器现在依赖于一个接口作为契约,而不是特定的实现。

接下来,为您的控制器创建第二个构造函数,该构造函数接受一个I​​InternalService(无论它被调用)并将其分配给_Internal。您的无参数构造函数仍然可以分配您现在正在使用的任何实例。

现在您已经拥有了这个配置(通常称为&#34;糟糕的mans依赖注入&#34;),您的单元测试可以创建一个控制器实例,传递服务的不同实现,从而引发异常。您可以通过创建新类来完成此操作,也可以使用Moq等库来动态执行此操作。

希望这是有道理的。