I am a front-end developer at Cirqle.nl
I am a member of CodeCatz
I tweet at @SvetkaSapelova
I get emails to svetsapelova@gmail.com
I come from Saint-Petersburg
I live in Ljubljana
..because we want to be sure our code works
..and because unit testing in Angularjs is pure fun
Oh hey, these are some notes. They'll be hidden in your presentation, but you can see them if you open the speaker notes window (hit 's' on your keyboard).Test the logic behind the components
Unit tests are all about mocks, stubs, spies, fakes, isolating …you name it
Angularjs is all about dependency injection
while(Dependency Injection) { Angularjs.easyToMock = true; }
...is a way to wire components of the app together
...enables loose coupling between the components
...is handled by Angularjs $injector service
Unit tests rely on the $injector to get all the components and ngMock module to mock angularjs services
'use strict'; angular.module('jsmeetupApp') .controller('MainCtrl', function ($scope, DataService) { //Chain the promise DataService .getData() .then(function(response){ $scope.items = response.data; }, function(err){ $scope.loadDataFailed = true; }); });
beforeEach(inject(function ($controller, $rootScope, $q) { scope = $rootScope.$new(); //create mock of the service MockDataService = { getData: function(){ defer = $q.defer(); return defer.promise } }; //initialize controller with mocked service MainCtrl = $controller('MainCtrl', { $scope: scope, DataService: MockDataService }); });
it('should attach a list of data to the scope', function () { //resolve the promise defer.resolve({data:['AngularJS', 'Emberjs', 'Backbone']}); //kick the angular digest loop to propagate promise resolution scope.$apply(); expect(scope.items.length).toEqual(3); }); it('should set loadDataFailed to true', function () { //reject the promise defer.reject(); scope.$apply(); expect(scope.loadDataFailed).toBeTruthy(); });
Don’t overload controllers with logic.
Slim controllers = testable controllers
It's possible to override Angularjs components by creating the new ones with the same name
beforeEach(module('jsmeetupApp', function($provide){ //this will override the original service $provide.service('DataService',function() { this.getData = function(){ defer = $q.defer(); return defer.promise }; }) })); beforeEach(inject(function ($controller, $rootScope, _$q_) { scope = $rootScope.$new(); $q = _$q_; MainCtrl = $controller('MainCtrl', {$scope: scope}); }));
'use strict'; angular.module('jsmeetupApp') .service('DataService', function($http){ this.getData = function(callback) { $http.get('api/getdata') .success(function(data){ callback(null,data) }) .error(function(err){ callback(err,null) }) }; });
/*in beforeEach inject $httpBackend */ /* and $injector */ $httpBackend.expectGET('api/getdata') .respond(200,['Angular']); DataService = $injector.get('DataService'); callback = jasmine.createSpy('callback'); .... it('should call the callback', function(){ DataService.getData(callback); //return trained response $httpBackend.flush(); expect(callback) .toHaveBeenCalledWith(null,['Angular']); });
they are reusable
angular.module('Dirs') .directive('clicked', function(){ return { link: function(scope,elem, attrs){ elem.bind('click', function(){ scope.clicked = true; scope.$apply() elem.hide(); }); }, template: '<button>Click Me</button>' } });
<div clicked></div>
make use of Angularjs $compile service
beforeEach(inject(function(_$compile_,$rootScope){ $compile = _$compile_; scope = $rootScope.$new(); //that is how Angularjs turns html into view element = $compile('<div clicked></div>')(scope); scope.$digest(); }));
$compile => $link => $digest
it('should display the button', function(){ //verify the template has been loaded expect(element.find('button').length).toEqual(1); }); it('should add display:none style', function(){ element.triggerHandler('click'); //we can chech the attrs of the element expect(element.css('display')).toEqual('none'); //we can get access to the scope of the element expect(element.scope().clicked).toBeTruthy(); });
Testem (test runner ) github.com/airportyh/testem Mocha (javascript test framework) github.com/mochajs/mocha QUnit (javascript test framework) qunitjs.com * zachlendon.github.io/blog/2013/03/26/quick-thoughts-on-testem-vs-testacular-karma techtalkdc.com/which-javascript-test-library-should-you-use-qunit-vs-jasmine-vs-mocha
ng-book.comng-newsletterdocs.angularjs.org/guide/unit-testinglinkis.com/smashingmagazine.com/kG2hs yearofmoo.com/2013/01/full-spectrum-testing-with-angularjs-and-karma.htmls