如何为下面的 Http 调用添加异常测试 returns a json
How to add an exception test for the below Http call that returns a json
[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
- root class
处理的异常
其中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
但肯定有更好的选择。
正如 Nkosi 在他的评论中提到的,您需要做的是向任何类型 _Internal
添加一个接口,以便您的控制器现在依赖于一个接口作为契约,而不是具体的实现。
接下来,为您的控制器创建第二个构造函数,它接受 IInternalService(不管它叫什么)并将其分配给 _Internal
。您的无参数构造函数仍然可以分配您现在使用的任何实例。
现在您有了这个配置(通常称为 "poor mans dependency injection"),您的单元测试可以创建一个控制器实例,传递一个抛出异常的服务的不同实现。您可以通过创建一个新的 class 来做到这一点,或者您可以使用像 Moq 这样的库动态地做到这一点。
希望这是有道理的。
被测方法有几个问题。
它在应重构为 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 问题。
[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
- root class
其中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
但肯定有更好的选择。
正如 Nkosi 在他的评论中提到的,您需要做的是向任何类型 _Internal
添加一个接口,以便您的控制器现在依赖于一个接口作为契约,而不是具体的实现。
接下来,为您的控制器创建第二个构造函数,它接受 IInternalService(不管它叫什么)并将其分配给 _Internal
。您的无参数构造函数仍然可以分配您现在使用的任何实例。
现在您有了这个配置(通常称为 "poor mans dependency injection"),您的单元测试可以创建一个控制器实例,传递一个抛出异常的服务的不同实现。您可以通过创建一个新的 class 来做到这一点,或者您可以使用像 Moq 这样的库动态地做到这一点。
希望这是有道理的。
被测方法有几个问题。
它在应重构为 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 问题。