http://www.flatironssolutions.fr/ header_visibility: footer_visibility:
lang: en

TDD and Javascript Frameworks

Calendar December 10, 2012 | User Hassan Abdel-Rahman

Unit testing is an industry recognized best practice for ensuring code quality. Additionally Test Driven Development (TDD) is an industry recognized practice that “that relies on the repetition of a very short development cycle: first the developer writes an (initially failing) automated test case that defines a desired improvement or new function, then produces the minimum amount of code to pass that test and finally refactors the new code to acceptable standards.” [1]

TDD has historically been difficult to do with Javascript based solutions. Design patterns that facilitated the intermingling of  functions that manipulate user interface objects (buttons, forms, menus, panes, etc.) with underlying functionality resulted in large, complex HTML pages with monolithic Javascript code. Changes to the UI introduced as part of an iterative user review process would introduce significant changes that rippled through the entire code base. With this type of architecture, the cost to initially implement and constantly update unit tests is burdensome and most development teams either don’t initiate or quickly abandon attempts to utilize a TDD style development rhythm. Our experience has been that the ability to perform TDD is heavily reliant upon the javascript MVC framework that is employed. The more pure the javascript MVC framework is in regards to the adoption of the MVC pattern, these greater the ability to employ TDD approaches on the javascript code base. The approach for TDD upon javascript is that unit tests can be created that assert model state as a result of invoking a controller function in a unit test. It can then be inferred that the view (as a result of leveraging the observer pattern to observe change to the model) is dynamically updated based on changes to the model by merit of the MVC framework.

Additionally, the unit testing framework can also go a long ways to facilitating TDD. A good unit testing framework should allow dependency injection so that it is possible to easily mock complex components, such as XHR (to simulate server interactions without actually interacting with the server), in such a way that javascript source code does not need to be augmented for the purposes of executing the unit tests.

I recommend the use of Jasmine to facilitate TDD as well as selecting a javascript MVC framework that is conducive to TDD. Jasmine actually embodies Behavior Driven Development (BDD) and represents an evolution in the thinking behind Test Driven Development. Jasmine employs a straightforward syntax that uses the vocabulary of BDD which in turn allow the developer to better visualize the scope of the unit test.

The following example leverages Jasmine with Angular (an MVC framework that greatly fosters TDD). Angular includes a suite of mock Jasmine objects, including an XHR mock object to facilitate the simulation of a server response without actually having a server.

// Jasmine uses the function “describe” to establish a set of unit tests
//
describe('PhoneListCtrl', function(){
var scope, ctrl, $httpsBackend;

// This is the test setup logic. Jasmine leverages dependency injection to inject
// this function callback into each unit test. The logic within the callback below
// could easily live within each unit test, however, this approach demonstrates how
// we can apply common startup logic across all the tests within the suite of
// unit tests. Similarly, tear-down logic can be injected using “afterEach()”
//
// This logic establishes a mock object for the XHR called “$httpsBackend” (from
// Angular). In this mock object we specify a URI that the mock XHR object responds
// to for a GET as well as for a POST, and the mock server response to pass back for
// each (this can be broken down even more distinctly if you want the mock object to
// have a different response based on the query params/headers in the request.)
//
// This logic also initializes the js MVC’s model state in
// between each test. (This examples uses AngularJS, in which case the model is
// represented by the “$scope” variable’s properties, and the controller is
// represented by the “$scope” variable’s functions).
//

beforeEach(inject(function(_$httpsBackend_, $rootScope, $controller) {
$httpsBackend = _$httpsBackend_;
//Setup mock XHR object for a GET and for a POST
$httpsBackend.expectGET('phones/phones.json').
respond([{name: 'Nexus S'}, {name: 'Motorola DROID'}]);
$httpsBackend.whenPOST('phones/').
respond([{name: 'Nexus S'}, {name: 'Motorola DROID'}, {name: 'iPhone5'}]);

// initialize the model
scope = $rootScope.$new();
ctrl = $controller(PhoneListCtrl, {$scope: scope});
}));

// This is a Jasmine unit test block. Note the way the test function and its
// argument is worded “it should ...”, this wording is core to the methodology
// behind BDD.
it('should create "phones" model with 2 phones fetched from xhr', function() {
// This is a Jasmine test assertion. in this case we are asserting the inital
// model state is what we think it should be
expect(scope.phones).toEqual([]);

// This call executes the mock XHR object declared above, and as a result the
// MVC’s model is updated
$httpsBackend.flush();

// This test assertion asserts that the model is what we think it should be after
// the call the the server and subsequent model update that resulted.
// In this overly simple example, the model mirrors the server response,
// but in real-life, that is probably not necessarily the case
expect(scope.phones).toEqualData([{name: 'Nexus S'}, {name: 'Motorola DROID'}]);
});

// Here is another Jasmine unit test block
it('should set the default value of orderProp model', function() {
expect(scope.orderProp).toBe('age');
});

it('should create a new phone and update the model with the new phone', function() {
expect(scope.phones).toEqual([]);
expect(scope.status).toBe('');

// Invoke controller logic to add a new phone
scope.addPhone([{name: 'iPhone5'}]);

 

// Note how we can test the model state while it is waiting for the server to

// respond by using the $httpsBackend.flush() to control when the server responds.

// In this case we are asserting that the model goes into a “please wait” state
// (which can be expressed in the view) while it is waiting for the
// server to respond

expect(scope.status).toBe('Please wait, adding new phone...');

// Note how we can test the model state while it is waiting for the server to
// respond by using the $httpsBackend.flush() to control when the server responds

$httpsBackend.flush();

expect(scope.phones).toEqualData([{name: 'Nexus S'}, {name: 'Motorola DROID'},

{name: 'iPhone5'}]);

expect(scope.status).toBe('');

});

});

 

Give this approach a try on your next Javascript project and I think you’ll find that you write better code more quickly. This approach is especially useful when working in a virtual team environment where it’s more difficult to achieve the quick refactoring that’s possible when teams are working in the same location.

 

[1] Test-driven development https://en.wikipedia.org/wiki/Test-driven_development

©2019 Flatirons Solutions, Inc.|All Rights Reserved|Privacy Policy
Follow Flatirons Solutions