someusernamehere
someusernamehere

Reputation: 75

value in unit testing controllers in isolation

Looking for guidance on unit testing controllers in asp.net web api 2. I have a standard multi-tier architecture: controllers -> servicelayer -> data access layer.

The service layer may throw exceptions originating from the service layer or exceptions from the data access layer [1]. Since the exception handling logic in each controller's method is pretty cookie cutter:

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

I opted to lift the try/catch & handling logic out of each of controller action and consolidate it in one place. One of the following would work: {IHttpActionInvoker, ExceptionFilter, or ExceptionHandler}.

Unfortunately, because the exception handling logic is now out of the controller my controller unit tests are no longer isolated at the IHttpActionResult level.

Previously I could test various inputs and expected outputs like:

This felt very clean conceptually, my controllers and unit tests all interact with well known IHttpActionResults (not service layer or lower concepts).

Now that I lifted the exception handling out to a global handler, the controller unit tests now throw a myriad of service or data access layer exceptions. Of course, I could achieve what I had previously with an integration test, e.g. ExceptionHandler + controller.

Question: In such a setup, what is the value in unit testing the controllers in isolation? Seems like a baseline value would be to have an integration test with ExceptionHandler + controller to at least validate all the status code's returned from various inputs.

[1] data acccess exceptions are generic and agnostic to a particular data access implementation. I didn't see a value to wrap it in a service layer exception.

Upvotes: 3

Views: 177

Answers (1)

StuartLC
StuartLC

Reputation: 107247

This doesn't really answer your question directly, but an alternative to your exception handling centralisation is to use a functional approach which accepts a lambda or closure:

(e.g. on a base controller or controller helper)

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

Which would then allow your controller methods to just 'code' the happy flow use-case for the Controller, and hence get the same DRY exception handling behaviour, but still allowing for the same unit-testability as you had previously:

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

I guess if there was custom catch error or finally block handling, you could overload the Invoker accordingly.

The benefit here is that you wouldn't need to repeat the exception handling unit test scenarios, and could focus the unit tests on the branches in the happy case controller flows.

Upvotes: 2

Related Questions