[HttpPost]
[Route("TnC")]
public IHttpActionResult TnC(CustomViewModel myViewModel)
{
try
{
return Json(_Internal.TnC(myViewModel, LoggedInUser));
}
catch (BusinessException exception)
{
return Json(BuildErrorModelBase(exception));
}
}
Where the _Internal
is a Service with a guaranteed 99.99% up-time and not formalized fault contract interfaces defined.
Exceptions which are handled in my application level(business layer level) as a BusinessException
- root class
Where BusinessException is defined as follows
public class BusinessException : Exception
{
BusinessException()...
BusinessExceptionFoo()...
BusinessExceptionBar()...
//...
}
And the current test Method is
To do : Add Exception test
[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
does not test the exception block of the code.
How do I construct a request that makes the service throw the exception other than manually removing the ethernet cable to get this specific type of server error.
The best I could come up with was
#if DEBUG && UnitTestExceptions
throw new BusinessException();
#endif
But there is gotta be a better option.
There are a few things of concern with the method under test.
It is mixing cross-cutting concerns in the action that should be refactored out into an ExceptionHandler
. Chances are that piece of code is repeated many times in that controller and others like it (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
}
}
The following extension method could be used to add the handler to the HttpConfiguration
during startup which also assumes that the application is taking advantage of dependency inversion services.
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;
}
Now that cross-cutting concerns have been dealt with the action becomes a lot simpler and easier to test. This is a simplified example of an 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));
}
}
where IBookingService
is defined as
public interface IBookingService {
BookingModel TnC(CustomViewModel viewModel, IPrincipal user);
}
Using a mocking framework like Moq an exception can be made to be thrown as needed.
[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
}
To test how that exception can be handled do a unit test on the exception handler and not the controller as that is not the responsibility of the controller.
Try to keep controllers lean and focused on its UI concerns.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With