Javascript Testing – Clean object mocking through JSON files

Clean code is what is over my head for the past few days. Thanks to an excellent course by John Papa, Angular JS Patterns: Clean Code on Pluralsight, it has made think why there is a constant need to improve in the way we write code. One thing that struck my mind is the separation of concern, which is essential not only in understanding the code, but also in maintaining the code over long periods of time. I am a big fan of testing my javascript ( … you should be too) and previously I have made an attempt to write clean tests to make them readable, scaleable and all that magic. The motivation behind writing that was a line we hear usually once every sprint, “Hey, lets add this …. “. My previous post outlines one approach but this time I have decided to take it one level up.

So, I will be using an overly simplistic Angular JS controller as a “proof of concept” to demonstrate how we can make our tests cleaner. Have a look at this controller, it contains a call to a service, through which it receives data and upon resolution of the promise, it attaches it to the view via the “vm” variable.

Note: I am using the “Controller As” syntax so bye bye $scope and hello cleanliness.

(function(){
'use strict';
angular.module('app.dashboard')
.controller('userProfile', userProfile);
userProfile.$inject = ['dataservice'];
function userProfile(dataservice){
var userProfile = this;
userProfile.name = "";
userProfile.age = "";
userProfile.email = "";
init();
//////////
function init(){
dataservice.getUserProfile().then(success);
function success(response){
userProfile.name = response.Name;
userProfile.age = response.Age;
userProfile.email = response.Email;
}
}
}
}());

To test this controller I needed to mock the call to the “dataservice” which returns a mocked response. There are 3 ways we can mock the response.

The Good: because it works!

Declare the mock object in the test file as a javascript variable. This approach is common but it makes the test file feel like spaghetti. Its hard to find stuff and you need to copy over the mock objects if they are used in more than one file. Yuck !

The Better: because we are getting there …

Declare the mock object in a mock Angular JS factory and expose it via a return object. This approach was blogged earlier here. The problem with this approach is that the mock Angular JS factory gets messier as the objects are added to it. It becomes one long/big file making it harder to maintain. Okay …. 

The Best: Bingo!

Declare the mock object in a JSON file, load it inside an Angular JS factory and expose it via a return object. I find this approach to be the best. Each object has its own file and is cleanly loaded into the Angular JS mock factory. The separation of objects into their own files make it a ton easier to maintain. That’s more like it ! 

In order to achieve the “Best” way to mock objects, we will make use of “jQuery-Jasmine” through which we will load the objects form the JSON file but it requires little bit of configuration so lets go through it step by step:

1. Install jquery-jasmine as a development dependency via npm

Run “npm install jquery-jasmine –save-dev” on the root of your project via terminal/bash/cmd.

2. Include jquery, jquery-jasmine as “files to be loaded”

Since I am using Karma to run my Jasmine tests, I only need a simple addition to the files array in my karma.conf.js file. See image below step 3.

3. Include the JSON files as “files to be loaded”

Similar to the previous step, add the JSON files to the files array in the karma.conf.js.

karma.conf.js

karma.conf.js

4. Let Jasmine know from where to load the JSON files from

I have a separate Angular JS module for my mocks, so the place where I declare my module is the place I use to declare the relative path of my tests which would be loaded on the browser.

Note: This path is something karma makes when it loads the tests from the file system onto the browser and can change in the upcoming releases.

 

Screen Shot 2014-11-10 at 12.06.06 am

This is it for the configuration, now we need to create an Angular JS factory which would load the JSON files and expose them as objects in our test files. Here is the JSON file and how we load it into our mock Angular JS factory …

[
{
"Name" :"Johnny Joker",
"Age" : 27,
"Email" : "johnnyjoker@pokerface.com"
}
]

(function(){
'use strict';
angular.module('SAPortal.mocks')
.factory('userProfileData', userProfileData);
var userProfile = getJSONFixture('userProfile.json')[0];
function userProfileData(){
return {
userProfile : userProfile
}
}
}());

And finally…. This is how the tests look like.

I must say, transitioning from good to better to best feels a lot satisfactory.

'use strict';
ddescribe('Controller: userProfile', function(){
beforeEach(module('SAPortal'));
beforeEach(module('app.dashboard'));
beforeEach(module('SAPortal.mocks'));
beforeEach(inject(init));
var _dataservice, _q, _userProfileData, _rootScope, _userProfile, _userProfileDeferred;
function init($injector, $controller){
_userProfileData = $injector.get('userProfileData');
_rootScope = $injector.get('$rootScope').$new();
_q = $injector.get('$q');
_dataservice = jasmine.createSpyObj('_dataservice', ['getUserProfile', 'getUserPermissions']);
_dataservice.getUserProfile.and.callFake(userProfilePromise);
function userProfilePromise(){
_userProfileDeferred = _q.defer();
return _userProfileDeferred.promise;
}
_userProfile = $controller('userProfile', {
dataservice: _dataservice
});
}
it('should set user profile on controller', setUserProfile);
function setUserProfile(){
_userProfileDeferred.resolve(_userProfileData.userProfile);
_rootScope.$digest();
expect(_userProfile.name).toBe("Johnny Joker");
expect(_userProfile.age).toBe(27);
expect(_userProfile.email).toBe("johnnyjoker@pokerface.com");
}
});

All of you can now safely get up from your chairs and do this ….