I’m all Moq’d out!

I’m currently working on creating a Hypermedia API, or at least getting as close as I can to one!

In order to avoid the client hitting a dead end when an error occurs, either internally within my code or as a result of a bad request from the client, I wanted to provide links that the client can follow. So, I created an ApiError model class to send in my response in such a case.

    [Hypermedia]
    public class ApiError : Resource
    {
        public string ParentId { get; set; }
        public string ErrorMessage { get; set; }
    }

The [Hypermedia] attribute is just a place holder I use within my Delegating Handler which “enriches” the resources with links. The ParentId is the id used in order to construct links to the home resource and it’s also used for constructing self links and so on.

The links are generated by way of Mark Seamann’s excellent HyprLinkr library, available via Nuget (http://nuget.org/packages/Hyprlinkr/).

My first attempt was to create an extension method to create the response, much like the built in HttpRequestMessage.CreateErrorResponse extension method. This worked great when I tested it using my current favourite chrome extension, POSTman. It returned the error, along with a “back” and “home” link so the client didn’t just hit a dead-end.

For example …

  "errorMessage" : "The was an error, run for the hills!",
  "_links" :
  [
    {
      "rel" : "home",
      "href" : "http://server/api/home/someone@example.com",
      "title" : "home",
      "verb" : "GET"
    }
  ]

So, all is well. I’m a happy bunny….

Until I run my unit tests.

What The Flip?

Why is there red everywhere?!

As it turns out I wrote some legacy (read: un-testable) code. Here’s what I’d come up with …

    public static class HttpRequestMessageExtensions
    {
        public static HttpResponseMessage CreateErrorResponse(
            this HttpRequestMessage request,
            HttpStatusCode statusCode,
            string parentId,
            string errorMessage,
            Action<IResourceLinker, ApiError> addLinks)
        {
            var apiError = new ApiError
            {
                ParentId = parentId,
                ErrorMessage = errorMessage
            };

            if (addLinks != null)
            {
                var routeLinker = _resourceLinkerFactory.GetResourceLinker(request);
                addLinks(routeLinker, apiError);
            }

            return request.CreateResponse(statusCode, apiError);
        }

After spending some time in the debugger trying to figure out why I was getting a null reference exception, I started looking at Mark’s code on github and eventually figured out that the GetRouteData() call was returning null. So, I spent more time trying to figure out a way of getting that to *not* be null. I ended up getting the source code for the aspnetwebstack from codeplex and looking at the code for GetRouteData().

Veering off-topic slightly, I didn’t have git installed on my pc, so I fired up powershell and …

  PS> cinst git

… I used chocolately to install it, restarted powershell and …

  PS> git clone https://git01.codeplex.com/aspnetwebstack

… I was rummaging around the asp.net web stack code πŸ˜€

Back on to topic …

After a few failed attempts at getting the null reference to go away it dawned on me that with another layer or two or abstraction I could Moq (on nuget too – https://www.nuget.org/packages/moq) my way out of trouble.

So, thinking about it for a little while I came up with a way of doing all this so I could moq it. Firstly I needed to change CreateApiErrorResponse from an extension method into a “service”, which I could then pass into the relevant controllers. Once I get past the unit test stage I could easily set it up using Castle Windsor. I would also need an IResourceLinker factory.

The Service

    public interface IHttpRequestMessageService
    {
        HttpResponseMessage CreateErrorResponse(
            HttpRequestMessage request,
            HttpStatusCode statusCode,
            string parentId,
            string errorMessage,
            Action<IResourceLinker, ApiError> addLinks);
    }

    public class HttpRequestMessageService : IHttpRequestMessageService
    {
        private readonly IResourceLinkerFactory _resourceLinkerFactory;

        public HttpRequestMessageService(IResourceLinkerFactory resourceLinkerFactory)
        {
            _resourceLinkerFactory = resourceLinkerFactory;
        }

        public HttpResponseMessage CreateErrorResponse(
            HttpRequestMessage request,
            HttpStatusCode statusCode,
            string parentId,
            string errorMessage,
            Action<IResourceLinker, ApiError> addLinks)
        {
            var apiError = new ApiError
            {
                ParentId = parentId,
                ErrorMessage = errorMessage
            };

            if (addLinks != null)
            {
                var routeLinker = _resourceLinkerFactory.GetResourceLinker(request);
                addLinks(routeLinker, apiError);
            }

            return request.CreateResponse(statusCode, apiError);
        }
    }

The Factory

    public interface IResourceLinkerFactory
    {
        IResourceLinker GetResourceLinker(HttpRequestMessage request);
    }

    public class ResourceLinkerFactory : IResourceLinkerFactory
    {
        public IResourceLinker GetResourceLinker(HttpRequestMessage request)
        {
            return new RouteLinker(request, new AttributeBasedRouteDispatcher());
        }
    }

Once I sorted out a few built errors it was time to do some Moqing…

    public class MyControllerTests
    {
        private Mock<IResourceLinkerFactory> _mockResourceLinkerFactory;
        private Mock<IResourceLinker> _mockResourceLinker;
        private IHttpRequestMessageService _httpRequestMessageService;
        private MyController _controller;

        [SetUp]
        public void SetUp()
        {
            const string id = "john.doe@example.com";

            var config = new HttpConfiguration();
            var request = new HttpRequestMessage(HttpMethod.Post, string.Format("http://localhost/api/user/{0}", id));

            _mockResourceLinkerFactory = new Mock<IResourceLinkerFactory>();
            _mockResourceLinker = new Mock<IResourceLinker>();
            _httpRequestMessageService = new HttpRequestMessageService(_mockResourceLinkerFactory.Object);

            _mockResourceLinkerFactory.Setup(x => x.GetResourceLinker(It.IsAny<HttpRequestMessage>()))
                                      .Returns(_mockResourceLinker.Object);

            _mockResourceLinker.Setup(x => x.GetUri(It.IsAny<Expression<Action<MyController>>>()))
                           .Returns(new Uri(string.Format("http://localhost/api/user/{0}", id)));

            _controller = new MyController(_httpRequestMessageService);
        }
    }

Hurrah! It all worked and I could get back to testing what I was actually trying to, which was unrelated to providing links when sending errors to the client. But, I needed this anyway πŸ˜€

Since the tests I was working on weren’t testing the presence of the links, I suppose I could have done something like this …

    var _mockHttpRequestMessageService = new Mock<IHttpRequestMessageService>();
    _mockHttpRequestMessageService
        .Setup(x => x.CreateErrorResponse(
            It.IsAny<HttpRequestMessage>(),
            It.IsAny<HttpStatusCode>(),
            It.IsAny<string>(),
            It.IsAny<string>(),
            It.IsAny<Action<IResourceLinker, ApiError>>())
        .Returns(new HttpResponseMessage(...));

Anyway, I hope that helps someone out. It’ll probably be me at some point in the future as my memory is not the best πŸ˜‰

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s