Telerik blogs

"Encapsulating what varies" is a core tenet of good programming practices. Ideally, we're doing that by abstracting "what varies" behind usable and friendly APIs. Making changes in an app with good abstractions typically means there's a much lower chance of missed bugs & a better understanding of the code base. While these abstractions are helpful when it's time to add new features, there's another benefit as well: what's abstracted can be mocked. On larger projects, where different team members work on features that are dependent on other team members' contributions, the ability to work as if the feature already exists, when in fact those developers haven't completed it, can keep overall progress chugging along. When it comes to web and mobile apps, one of the classic "divides" occurs between client and server. You might have developers focusing solely on the front end or mobile app, with others focusing on server-side concerns, like web services. Have you ever been on a team where the client-focused developers had to wait for server-side devs to complete work on HTTP services before they could continue on? This does not have to be the case.

Mocking HTTP services itself isn't difficult – and depending on your client side architectural choices, you can also mock your data using your models/view models. Let's take a peek at a couple of those options.

Mocking $.ajax() with Mockjax

Mockjax is a very handy library written and maintained by the team at appendTo. It patches the jQuery $.ajax() call, enabling you to selectively mock HTTP responses. As far as your app is concerned, it thinks it's making a call to external resource (i.e. - the server that isn't ready yet). Instead, Mockjax will intercept the HTTP request if it matches one of the mocks you've set up. This gives you all kinds of control in testing your service calls.

Simple Mockjax

Let's pretend our app needs to display a list of people, and when we select one of those people, our app will display information about them. Our server-side team has informed us that there will be two endpoints: one to get the list of people (containing first name, last name and an ID), and another to get detailed information about a person. Our server-side devs aren't ready yet with the HTTP endpoints, so let's mock what that service's reponse would look like (btw - this code is runnable here):

I'm intentionally splitting this into two service calls, although it's reasonable to expect some teams to send all of this data in one call, and to filter the data on the client when you need details for an individual.

(function (global, $) {
    // a in-memory list of people that we'll use in our mocks
    // (this represents the data we'd get if we combined both the
    // call to get a list of people as well as details for each one)
    var people = [{
        lastName: "Holland",
        firstName: "Burke",
        id: 0,
        city : "Nashville",
        state: "Tennessee"
    }, {
        lastName: "Bailey",
        firstName: "Derick",
        id: 1,
        city : "Waco",
        state: "Texas"
    }, {
        lastName: "Cowart",
        firstName: "Jim",
        id: 2,
        city : "Chattanooga",
        state: "Tennessee"
    }, {
        lastName: "VanToll",
        firstName: "TJ",
        id: 3,
        city : "Lansing",
        state: "Michigan"
    }];

    // Mocking an endpoint to get a list of people.
    // Notice we're mapping over our above collection
    // of people to return only what our service would
    $.mockjax({
        url: 'api/people',
        dataType: 'json',
        type: 'GET',
        response: function() {
            this.responseText = $.map(people, function(val) {
                return { 
                    firstName: val.firstName,
                    lastName : val.lastName,
                    id       : val.id
                }
            });
        }
    });
    
    // mocking an endpoint to get one person
    $.mockjax({
        url: /api\/person\/([0-9]{1,4})+/,
        urlParams: ['personId'],
        dataType: 'json',
        type: 'GET',
        response: function(settings) {
            this.responseText = people[settings.urlParams.personId];
        }
    });
    
    // a Kendo datasource for getting a set of people
    var peopleDataSrc = new kendo.data.DataSource({
        transport: {
            read: {
                type: "GET",
                url: "api/people",
                contentType: "application/json; charset=utf-8",
                dataType: "json"
            }
        },
        type: "json"
    });
    
    // simple function wrapping a call to get a single person
    // (just showing an alternate way than a data source)
    var getPerson = function (id, viewModel) {
        global.app.kapp.showLoading();
        $.ajax({
            url: "api/person/" + id,
            dataType: "json",
            success: function (data) {
                viewModel.set("firstName", data.firstName);
                viewModel.set("lastName", data.lastName);
                viewModel.set("city", data.city);
                viewModel.set("state", data.state);
                global.app.kapp.hideLoading();
            }
        });
    };
    
    // this will be bound to our person view
    var PersonViewModel = kendo.Class.extend({
        init: function () {
            this.viewModel = kendo.observable({
                firstName: null,
                lastName : null,
                city     : null,
                state    : null
            });
        },
        
        fetchPerson : function(e) {
            getPerson( e.view.params.id, this.model );
        }
    });
    
    global.app = {
        people : peopleDataSrc,
        person : new PersonViewModel(),
        kapp: new kendo.mobile.Application(document.body,{
            initial: "people"
        })
    };
}(window, jQuery));

Let's break the above code down:

  • We're creating an in-memory list of "people" to use in place of the back-end database that will eventually be available once our server-side devs have stood up those endpoints.
  • We define two mocked endpoints using mockjax.
    • The first one simply intercepts any AJAX request to api/people using the GET method. This mock will reply with JSON (hence the dataType: "json" value) - and the response will be the first name, last name and id of each person in the "people" list.
    • The second mock uses a regex to match any request to api/person/ where it's followed by a number ranging from 0 to 9999. We're using a "capturing" regular expression here and we've told mockjax the urlParams we want it to keep track of. This is so we can access the personId value of the selected person later as we create the response.
  • Then we define a Kendo datasource used by a listview to display the list of people.
  • We create a view model to hold data for an individual person (including the fetchPerson method, which makes use of the getPerson helper function).

These are two very simple mocks with mockjax, but you can already see the power – especially since the second mock covers us for ANY user retrieved (can you imagine if we'd had to mock for each one? YUCK!). But we're just scratching the surface here!

Going Further with mockjax

The beauty of using a library like mockjax isn't just in the fact that you don't have to wait on server side endpoints to have a functional & "demo-able" client UI. It's also because this allows you to test error conditions in the client. What happens if a request takes too long? How does your client handle something other than a 200 OK response?

Simulating Long Response Times

It's a good idea to have some sort of client side behavior kick in when a request takes longer than usual – after all, we don't want the user to think we've forgotten about them, or that the app has frozen. If you want to simulate the call to get a person taking 7 seconds, you'd alter the second mock to look like this:

$.mockjax({
    url: /api\/person\/([0-9]{1,4})+/,
    urlParams: ['personId'],
    responseTime: 7000,
    dataType: 'json',
    type: 'GET',
    response: function(settings) {
        this.responseText = people[settings.urlParams.personId];
    }
});

If you want to go all the way and simulate a timeout:

$.mockjax({
    url: /api\/person\/([0-9]{1,4})+/,
    urlParams: ['personId'],
    isTimeout: true,
    dataType: 'json',
    type: 'GET',
    response: function(settings) {
        this.responseText = people[settings.urlParams.personId];
    }
});

Everything's Not Always Going to be "200 OK"

If we want to simulate a different status than the default of 200, we just need to add a status property to our mock:

$.mockjax({
    url: /api\/person\/([0-9]{1,4})+/,
    urlParams: ['personId'],
    status: 500,
    dataType: 'json',
    type: 'GET',
    response: function(settings) {
        this.responseText = { error: "You fail at all the things...." };
    }
});

It's also possible to defer the decision on which response code to use until the response method fires:

$.mockjax({
    url: /api\/person\/([0-9]{1,4})+/,
    urlParams: ['personId'],
    isTimeout: true,
    dataType: 'json',
    type: 'GET',
    response: function(settings) {
        var person = people[settings.urlParams.personId];
        if(person) {
            this.responseText = person;
        } else {
            this.status = 404;
            this.responseText = { error: "Perhaps that person left to go find themselves." };
        }
    }
});

Check it Out

Like I said, we've only scratched the surface, but you can read more about mockjax here. If you're app is using jQuery, it's hard to beat the gains brought on by using mockjax – not only to enable your client-side team to continue working, but for unit-testing, simulating error conditions and more.

It's possible to use mockjax with other projects that will generate mock data - expanding the kinds of power behind (and the breadth of) your sample data. However, it's also possible for us to mock the data we're using in this simple example app at the datasource & viewmodel level.

Mocking Data Using Kendo DataSources & View Models

So how would we go about mocking the data for this example app without mocking at the HTTP request level? Something like this (see this fiddle to see it in action):

(function (global, $) {
    // a in-memory list of people that we'll use in our mocks
    // (this represents the data we'd get if we combined both the
    // call to get a list of people as well as details for each one)
    var people = [{
        lastName: "Holland",
        firstName: "Burke",
        id: 0,
        city : "Nashville",
        state: "Tennessee"
    }, {
        lastName: "Bailey",
        firstName: "Derick",
        id: 1,
        city : "Waco",
        state: "Texas"
    }, {
        lastName: "Cowart",
        firstName: "Jim",
        id: 2,
        city : "Chattanooga",
        state: "Tennessee"
    }, {
        lastName: "VanToll",
        firstName: "TJ",
        id: 3,
        city : "Lansing",
        state: "Michigan"
    }];
    
    // a Kendo datasource for getting a set of people
    var peopleDataSrc = new kendo.data.DataSource({
        data: $.map(people, function(val) {
            return { 
                firstName: val.firstName,
                lastName : val.lastName,
                id       : val.id
            }
        })
    });
    
    // simple function wrapping a call to get a single person
    // (just showing an alternate way than a data source)
    var getPerson = function (id, viewModel) {
        global.app.kapp.showLoading();
        var person = people[id];
        viewModel.set("firstName", person.firstName);
        viewModel.set("lastName", person.lastName);
        viewModel.set("city", person.city);
        viewModel.set("state", person.state);
        global.app.kapp.hideLoading();
    };
    
    // this will be bound to our person view
    var PersonViewModel = kendo.Class.extend({
        init: function () {
            this.viewModel = kendo.observable({
                firstName: null,
                lastName : null,
                city     : null,
                state    : null
            });
        },
        
        fetchPerson : function(e) {
            getPerson( e.view.params.id, this.model );
        }
    });
    
    global.app = {
        people : peopleDataSrc,
        person : new PersonViewModel(),
        kapp: new kendo.mobile.Application(document.body,{
         initial: "people"
     })
    };
}(window, jQuery));

So what's different?

  • We're still using our in-memory list of people.
  • Instead of using mockjax to mock an endpoint returning a list of people, we've changed our peopleDataSrc by removing the transport information and instead providing data (for local data).
  • Instead of using mockjax to mock the person endpoint, we've changed our getPerson method to look up the person in our people list.

When we're ready to introduce the real web services into our app, we'd just have to change our data source definition, and update getPerson to make an HTTP request.

It's All About Trade-Offs

This approach works just fine - and would enable you to easily continue work on the client-side until the "real" server endpoints were available. While it might be easier to forego mocking the actual http endpoints, you've traded a small amount of complexity for less flexibility. For example - this approach assumes success all around. In fact, you might have noticed a potential problem in how I refactored getPerson. See it?

It's no longer asynchronous. Once the server endpoints are available, we'd refactor getPerson to make an AJAX call. The only problem is that we might have – in the meantime – baked in some downstream behavior that assumes the call will be synchronous. That will change (and potentially break our app, or at least surprise us) once we introduce the AJAX call to the actual endpoint.

Which Route to Take?

As in all wise answers to development questions: It depends.

Kendo UI isn't the only client-side framework capable of mocking data sources. You can do similar things in Backbone and Knockout, for example. I've personally found mocking at the model/data source level useful when I simply need to see how a UI component looks, and tweak styling, etc. However, if it's going to be any length of time before the server endpoints are available, I'm going to go for something like mockjax. In my opinion, mocking at that level provides the best testability without introducting unintentional "fragility" into the app. It's also quite simple to include or exclude mockjax and your mocked requests by grouping your mocks into one file. When you no longer need them, you remove the script tags that source mockjax and your mocked requests.


About the Author

Jim Cowart

Jim Cowart is an architect, developer, open source author, and overall web/hybrid mobile development geek. He is an active speaker and writer, with a passion for elevating developer knowledge of patterns and helpful frameworks. 

Related Posts

Comments

Comments are disabled in preview mode.