Thursday, March 6, 2014

Testing your HAPI service

For the past few weeks I've been building a REST API with hapi. It's a bit of a mind shift from middleware based frameworks like Express or Django but so far I'm very happy with it. The design really shows that it was made for building serious services.

As far as testing goes I couldn't find much material on how to go about and test my API with, say something like Mocha. I suppose I could've just browsed the hapi source code and see how it was tested internally, but what I ended up doing (after some googling) was that I'd actually start the hapi server before each test case and then stop it after each test. In the tests I used mikeal's request library to query the API. This worked but it felt a bit awkward compared to, for example, what Django provides.

Luckily, I ran into Eran's great talk about hapi where he briefly mentions the server.inject() method. I remember seeing that method before but didn't really understand what it would be good for. But actually with that method you can easily build your API tests.  So, I created a generic function for querying  my API:

/**
 * @param {hapi.Server} server - hapi server instance
 * @param {object} requestobj - options for making the request
 */
exports.request = function (server, requestobj, callback)
    server.inject(requestobj, function (res)
        if (res.payload)
            res.body = JSON.parse(res.payload);
        }
        callback(res);
    });



In the tests I would do something like this (note that the following test uses Mocha but any test framework should do just fine):


    beforeEach(function (done) {
        var api = require('../api/api.js');
        this.server = api.createServer();
        done();
    });


    it('should GET all players', function (done) {
        var request = { url: '/player/', method: 'GET' };
        test.request(this.server, request, function (res) {
            res.statusCode.should.be.equal(200);
            res.body.length.should.be.equal(6);
            done();
        });
    });

And that was it. No more starting and stopping the server all the time.


Wednesday, February 26, 2014

Ember, Leaflet and Leaflet.Draw

I spent some time trying to figure out how to hook all these components together and decided to document the process here in case someone else is struggling with this as well.

I had a minimalistic Ember project in which I wanted to use Leaflet maps. I quickly found out about the ember-leaflet project. I added the ember-leaflet.js dependency to my index.html and following code into my ember application:

App.TileLayer = EmberLeaflet.TileLayer.extend({
    tileUrl: 'http://localhost:7000/tiles/{z}/{x}/{y}.png'
});
App.MapView = EmberLeaflet.MapView.extend({
    childLayers: [App.TileLayer],
    center: [34.227, -118.55],
    zoom: 11
});
Only problem I had with this, was that the leaflet map was taking too much space. Basically it filled the whole ember view. So, when I had other stuff in my handlebars template (besides the  <div id="map"></div> placeholder), the map would fill the whole template pouring over the other elements that I had in my template. This is why I ended up creating another ember view inside the outer view that would only contain the map. Then in the parent template I would have all my html elements as before but where I previously had the div-placeholder for the map, I put {{render "map"}} and then in map.hbs file just the map-div:  <div id="map"></div>.

So far so good.

I also needed draw controls and sure enough, Leaflet.Draw had everything I wanted. I added the css and js files into my project and added following options to my map configuration:

App.MapView = EmberLeaflet.MapView.extend({
   childLayers: [App.TileLayer],
   center: [34.227, -118.55],
   zoom: 11,
   options: {
       drawControl: true
   }
});
That would show the controls on the map just fine, but I wanted to get the edit controls working as well and as Leaflet.Draw documentation tells us I needed to create the Draw controls myself. I wasn't quite sure where to put that code so I ended up overriding the didCreateLayer and initialising the drawing controls there. But I still wasn't able to hook to all the events that Leaflet.Draw provides. Of course, I probably could have gotten the leaflet map handle from ember-leaflet and attach my event handler directly there, but that seemed like a hack. Going through the ember-leaflet code I noticed that it internally uses concatenatedProperties: ['events'] to gather all supported events. After that I just introduced that property in my derived view and added all Leaflet.Draw events there. At that point the code looked like this:

App.MapView = EmberLeaflet.MapView.extend({
    childLayers: [App.TileLayer],
    center: [34.227, -118.55],
    
    zoom: 11,

    events: [
        'draw:created',
        'draw:edited',
        'draw:deleted',
        'draw:drawstart',
        'draw:drawstop',
        'draw:editstart',
        'draw:editstop',
        'draw:deletestart',
        'draw:deletestop'
    ],

    didCreateLayer: function () {
        this._super();

        var map = this.get('layer'),
            drawnItems = new L.FeatureGroup(),
            drawControl;


        this.set('drawnItems', drawnItems);
        map.addLayer(this.drawnItems);
        drawControl = new L.Control.Draw({
            edit: {
                featureGroup: this.drawnItems
            }
        });
        map.addControl(drawControl);
    },

    "draw:created": function (e) {
        var layer = e.layer,
            type = e.layerType,
            drawnItems = this.get('drawnItems'),
            bounds;

        drawnItems.addLayer(layer);
        return this;
    }
});

That was it. Now I'm able to get all the Leaflet.Draw events to my view.



Monday, February 17, 2014

Doctor of Lego

So, I finally got my Ph.D.

With Professor Cesare Pautasso from University of Lugano being the opponent in my defence, we had good discussions. The topic of my dissertation is "Engineering Web Applications: Architectural Principles for Web Software". In case you are interested in getting the pdf file, here's the link.



The whole process took couple of more years than I was hoping for because I was really caught up with the implementation side of these things in the company I work for. It would've been nice to publish this few years earlier but no harm done. If you're looking for a TL;DR version of my dissertation this post is a very brief summary of the subject.