Last active
August 29, 2015 14:06
-
-
Save crucialfelix/26afe7293ffcd3e0d6db to your computer and use it in GitHub Desktop.
Angular Speedball - a jasmine testing utility to eliminate boilerplate in testing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
'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