Skip to content

Instantly share code, notes, and snippets.

@crucialfelix
Last active August 29, 2015 14:06
Show Gist options
  • Star 4 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save crucialfelix/26afe7293ffcd3e0d6db to your computer and use it in GitHub Desktop.
Save crucialfelix/26afe7293ffcd3e0d6db to your computer and use it in GitHub Desktop.
Angular Speedball - a jasmine testing utility to eliminate boilerplate in testing
'use strict';
/**
*
* An Angular Jasmine testing utility
*
* @copyright 2013 Chris Sattinger
* MIT License
*
* automatically injects these services:
* '$httpBackend', '$rootScope', '$controller', '$compile'
describe('app.controllers', function() {
var s = new Speedball(
[
// require these modules
'app.services'
], [
// load these services to s.{Account}
'Comments',
'$routeParams',
]);
describe('CommentsCtrl', function() {
var data = [{
"comment": "hi",
'user': {
_id: 'userid'
}
}];
it('should fetch comments', function() {
// this is what we expect the controller to fetch
// and we supply the data it should get
s.willGET('/capsules/1/comments', data);
// call the control
s.$routeParams.id = 1;
s.controller('CommentsCtrl');
// having executed the controller
// we can test the s.$scope
expect(s.$scope.comments).toEqualData(data);
});
});
});
**/
function Speedball(modules, injections, spies, templates) {
var self = this;
var moduleParams = {};
// preloading templates as compiled modules using karma's html2js preprocessor
if(templates) {
angular.forEach(templates, function(templateURLs, baseTemplatePath) {
angular.forEach(templateURLs, function(templateURL) {
// where karma preprocessor loaded it into
var full = baseTemplatePath + templateURL;
// module name is full/path/to/the.html
modules.push(full);
});
});
}
angular.forEach(modules, function(module) {
moduleParams[module] = [module];
});
angular.forEach(spies, function(spy) {
var module = spy[0],
service = spy[1],
methods = spy[2];
if(!moduleParams[module]) {
moduleParams[module] = [module];
}
moduleParams[module].push(function($provide) {
$provide.decorator(service, function ($delegate) {
angular.forEach(methods, function(method) {
$delegate[method] = jasmine.createSpy();
});
return $delegate;
});
});
});
// modules need to be sorted. do the spy ones first
if(!injections) {
injections = [];
}
injections = injections.concat(['$httpBackend', '$rootScope', '$controller', '$compile', '$templateCache']);
beforeEach(function() {
angular.forEach(moduleParams, function(moduleParam) {
module.apply(undefined, moduleParam);
});
this.addMatchers({
toEqualData: function(expected) {
return angular.equals(this.actual, expected);
}
});
});
beforeEach(inject(function($injector) {
angular.forEach(injections, function(name) {
self[name] = $injector.get(name);
});
// always make a $scope
self.$scope = self.$rootScope.$new();
if(templates) {
angular.forEach(templates, function(templateURLs, baseTemplatePath) {
angular.forEach(templateURLs, function(templateURL) {
var full = baseTemplatePath + templateURL;
// copy it ...
var template = self.$templateCache.get(full);
// to the URL the directive will request it at
self.$templateCache.put(templateURL, template);
});
});
}
}));
// always verify all requests
this.verifyRequests();
}
Speedball.prototype.verifyRequests = function() {
var self = this;
afterEach(function() {
self.$httpBackend.verifyNoOutstandingExpectation();
self.$httpBackend.verifyNoOutstandingRequest();
// window.localStorage.clear();
});
};
Speedball.prototype.willGET = function(url, response, statusCode) {
return this.$httpBackend
.expectGET(url)
.respond(statusCode || 200, response);
};
Speedball.prototype.willPOST = function(url, post, response, statusCode) {
return this.$httpBackend
.expectPOST(url, post)
.respond(statusCode || 201, response);
};
Speedball.prototype.willPUT = function(url, post, response, statusCode) {
return this.$httpBackend
.expectPUT(url, post)
.respond(statusCode || 200, response);
};
Speedball.prototype.flush = function() {
this.$httpBackend.flush();
};
// make controller and flush expected requests
Speedball.prototype.controller = function(controllerName, dontFlush) {
var ctlr = this.$controller(controllerName, {$scope: this.$scope});
if(!dontFlush) {
this.flush();
}
return ctlr;
};
/**
*
* For pre-loading templates for directive testing
*
* because angular mock $httpBackend will intercept all requests
* you need a way to let the directive fetch its template.
*
* in your karma.conf add the partials to your files list:
* // load partials for testing directives
* // using html2js preprocessor
* 'app/partials/*.html',
*
* and add the karma preprocessor:
* // generate js files from html templates to expose them during testing.
* preprocessors = {
* 'app/partials/*.html': 'html2js'
* };
*
* then add those templates to the speedball:
*
* templates: {
* 'phoneapp/yapp/': [
* 'partials/capsule-summary.html'
* ]
* }
*
* now you will be able to compile directives
**/
Speedball.prototype.directive = function(html, scopeVars, testFunc) {
var htmlEl = angular.element(html), el, self = this, directiveScope;
if(scopeVars) {
angular.forEach(scopeVars, function(v, k) {
self.$scope[k] = v;
});
}
el = this.$compile(htmlEl)(this.$scope);
this.$scope.$digest();
this.$scope.$apply();
if(testFunc) {
// children scope is that inner scope created by the directive
// else it uses the scope it was given by parent
// warning: this may not be perfect yet
directiveScope = el.children().scope() || el.scope();
testFunc(el, directiveScope);
}
return el;
};
Speedball.prototype.debug = function(obj) {
try {
// circular references are popular in angular
// but they cannot be printed
console.log(JSON.stringify(obj));
} catch(err) {
console.log('' + obj + '::');
for (var key in obj) {
if (obj.hasOwnProperty(key)) {
console.log(key + '=' + obj[key]);
}
}
}
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment