Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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));
    }
}

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.

like image 691
DL Narasimhan Avatar asked Mar 22 '17 11:03

DL Narasimhan


1 Answers

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.

like image 162
Nkosi Avatar answered Oct 24 '22 16:10

Nkosi