Monday 6 October 2014

Caching and Cache-Busting in AngularJS with HTTP interceptors

Loading On-Demand and Caching

I've written before about my own needs for caching and cache-busting when using RequireJS. Long story short, when I'm loading static resources (scripts / views etc) on demand from the server I want to do a little URL fiddling along the way. I want to do that to cater for these 2 scenarios:

  1. In Development - I want my URLs for static resources to have a unique querystring with each request to ensure that resources are loaded afresh each time. (eg so a GET request URL might look like this: "/app/layout/sidebar.html?v=IAmRandomYesRandomRandomIsWhatIAm58965782")
  2. In Production - I want my URLs for static resources to have a querystring with that is driven by the application version number. This means that static resources can potentially be cached with a given querystring - subsequent requests should result in a 304 status code (indicating “Not Modified”) and local cache should be used. But when a new version of the app is rolled out and the app version is incremented then the querystring will change and resources will be loaded anew. (eg a GET request URL might look like this: "/app/layout/sidebar.html?v=1.0.5389.16180")

Loading Views in AngularJS Using this Approach

I have exactly the same use cases when I'm using AngularJS for views. Out of the box with AngularJS 1.x views are loaded lazily (unlike controllers, services etc). For that reason I want to use the same approach I've outlined above to load my views. Also, I want to prepend my URLs with the root of my application - this allows me to cater for my app being deployed in a virtual folder.

It turns out that's pretty easy thanks to HTTP interceptors. They allow you to step into the pipeline and access and modify requests and responses made by your application. When AngularJS loads a view it's the HTTP service doing the heavy lifting. So to deal with my own use case, I just need to add in an HTTP interceptor that amends the get request. This is handled in the example that follows in the configureHttpProvider function: (The example that follows is TypeScript - though if you just chopped out the interface and the type declarations you'd find this is pretty much idiomatic JavaScript)


interface config {
    appRoot: string;              // eg "/"
    inDebug: boolean;             // eg true or false
    urlCacheBusterSuffix: string; // if in debug this might look like this: "v=1412608547047", 
                                  // if not in debug this might look like this: "v=1.0.5389.16180"
}

function configureHttpProvider() {

    // This is the name of our HTTP interceptor 
    var serviceId = "urlInterceptor"; 

    // We're going to create a service factory which will be our HTTP interceptor
    // It will be injected with a config object which is represented by the config interface above
    app.factory(serviceId, ["$templateCache", "config", 
        function ($templateCache: ng.ITemplateCacheService, config: config) {

        // We're returning an object literal with a single function; the "request" function
        var service = {
            request: request
        };

        return service;

        // Request will be called with a request config object which includes the URL which we will amend
        function request(requestConfig: ng.IRequestConfig) {

            // For the loading of HTML templates we want the appRoot to be prefixed to the path
            // and we want a suffix to either allow caching or prevent caching 
            // (depending on whether in debug mode or not)
            if (requestConfig.method === "GET" && endsWith(requestConfig.url, ".html")) {

                // If this has already been placed into a primed template cache then we should leave the URL as is
                // so that the version in templateCache is served.  If we tweak the URL then it will not be found
                var cachedAlready = $templateCache.get(requestConfig.url);
                if (!cachedAlready) {
                    // THIS IS THE MAGIC!!!!!!!!!!!!!!!

                    requestConfig.url = config.appRoot + requestConfig.url + config.urlCacheBusterSuffix;
                
                    // WE NOW HAVE A URL WHICH IS CACHE-FRIENDLY FOR OUR PURPOSES - REJOICE!!!!!!!!!!!
                }
            }

            return requestConfig;
        }

        // a simple JavaScript string "endswith" implementation
        function endsWith(str: string, suffix: string) {
            return str.indexOf(suffix, str.length - suffix.length) !== -1;
        }
    }]);

    // This adds our service factory interceptor into the pipeline
    app.config(["$httpProvider", function ($httpProvider: ng.IHttpProvider) {
        $httpProvider.interceptors.push(serviceId);
    }]);
}

This interceptor steps in and amends each ajax request when all the following conditions hold true:

  1. It's a GET request.
  2. It's requesting a file that ends ".html" - a template basically.
  3. The template cache does not already contain the template. I left this out at first and got bitten when I found that the contents of the template cache were being ignored for pre-primed templates. Ugly.

Interesting technique.... How do I apply it?

Isn't it always much more helpful when you can see an example of code in the context of which it is actually used? Course it is! If you want that then take a look at app.ts on GitHub. And if you'd like the naked JavaScript well that's there too.

Friday 3 October 2014

He tasks me; he heaps me.... I will wreak that MOQ upon him.

Enough with the horrific misquotes - this is about Moq and async (that's my slight justification for robbing Herman Melville).

It's pretty straightforward to use Moq to do async testing thanks to it's marvellous ReturnsAsync method. That means it's really easy to test a class that consumes an async API. Below is an example of a class that does just that: (it so happens that this class is a Web API controller but that's pretty irrelevant to be honest)


namespace Proverb.Web.Controllers
{
    // ISageService included inline for ease of explanation
    public interface ISageService
    {
        Task<int> DeleteAsync(int id);
    }

    public class SageController : ApiController
    {
        ISageService _sageService;

        public SageController(ISageService userService) 
        {
            _sageService = userService;
        }

        public async Task<IHttpActionResult> Delete(int id)
        {
            int deleteCount = await _sageService.DeleteAsync(id);

            if (deleteCount == 0)
                return NotFound();
            else
                return Ok();
        }
   }
}

To mock the _sageService.DeleteAsync method it's as easy as this:


namespace Proverb.Web.Tests.ASPNet.Controllers
{
    [TestClass]
    public class SageControllerTests
    {
        private Mock<ISageService> _sageServiceMock;
        private SageController _controller;

        [TestInitialize]
        public void Initialise()
        {
            _sageServiceMock = new Mock<ISageService>();

            _controller = new SageController(_sageServiceMock.Object);
        }

        [TestMethod]
        public async Task Delete_returns_a_NotFound()
        {
            _sageServiceMock
                .Setup(x => x.DeleteAsync(_sage.Id))
                .ReturnsAsync(0); // This makes me *so* happy...

            IHttpActionResult result = await _controller.Delete(_sage.Id);

            var notFound = result as NotFoundResult;
            Assert.IsNotNull(notFound);
            _sageServiceMock.Verify(x => x.DeleteAsync(_sage.Id));
        }

        [TestMethod]
        public async Task Delete_returns_an_Ok()
        {
            _sageServiceMock
                .Setup(x => x.DeleteAsync(_sage.Id))
                .ReturnsAsync(1); // I'm still excited now!

            IHttpActionResult result = await _controller.Delete(_sage.Id);

            var ok = result as OkResult;
            Assert.IsNotNull(ok);
            _sageServiceMock.Verify(x => x.DeleteAsync(_sage.Id));
        }
    }
}

But wait.... What if there's like... Nothing?

Nope, I'm not getting into metaphysics. Something more simple. What if the async API you're consuming returns just a Task? Not a Task of int but a simple old humble Task.

So to take our example we're going from this:


    public interface ISageService
    {
        Task<int> DeleteAsync(int id);
    }

To this:


    public interface ISageService
    {
        Task DeleteAsync(int id);
    }

Your initial thought might be "well that's okay, I'll just lop off the ReturnsAsync statements and I'm home free". That's what I thought anyway.... And I was *WRONG*! A moments thought and you realise that there's still a return type - it's just Task now. What you want to do is something like this:


            _sageServiceMock
                .Setup(x => x.DeleteAsync(_sage.Id))
                .ReturnsAsync(void); // This'll definitely work... Probably

No it won't - void is not a real type and much as you might like it to, this is not going to work.

So right now you're thinking, well Moq probably has my back - it'll have something like ReturnsTask, right? Wrong! It's intentional it turns out - there's a discussion on GitHub about the issue. And in that discussion there's just what we need. We can use Task.Delay or Task.FromResult alongside Moq's good old Returns method and we're home free!

Here's one I made earlier...


namespace Proverb.Web.Controllers
{
    // ISageService again included inline for ease of explanation
    public interface ISageService
    {
        Task DeleteAsync(int id);
    }

    public class SageController : ApiController
    {
        ISageService _sageService;

        public SageController(ISageService userService) 
        {
            _sageService = userService;
        }

        public async Task<IHttpActionResult> Delete(int id)
        {
            await _sageService.DeleteAsync(id);

            return Ok();
        }
   }
}


namespace Proverb.Web.Tests.ASPNet.Controllers
{
    [TestClass]
    public class SageControllerTests
    {
        private Mock<ISageService> _sageServiceMock;
        private SageController _controller;

        readonly Task TaskOfNowt = Task.Delay(0);
        // Or you could use this equally valid but slightly more verbose approach:
        //readonly Task TaskOfNowt = Task.FromResult<object>(null);

        [TestInitialize]
        public void Initialise()
        {
            _sageServiceMock = new Mock<ISageService>();

            _controller = new SageController(_sageServiceMock.Object);
        }

        [TestMethod]
        public async Task Delete_returns_an_Ok()
        {
            _sageServiceMock
                .Setup(x => x.DeleteAsync(_sage.Id))
                .Returns(TaskOfNowt); // Feels good doesn't it?

            IHttpActionResult result = await _controller.Delete(_sage.Id);

            var ok = result as OkResult;
            Assert.IsNotNull(ok);
            _sageServiceMock.Verify(x => x.DeleteAsync(_sage.Id));
        }
    }
}