diff --git a/README.md b/README.md index 43f886b2..5ee5b2cf 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # AngularJS Style Guide -*Opinionated AngularJS style guide for Liberty Seguros Compania de Seguros y Reaseguros S.A. (lu.pl) teams +*Opinionated AngularJS style guide for Liberty Seguros Compania de Seguros y Reaseguros S.A. (lu.pl) teams.* If you are looking for an opinionated style guide for syntax, conventions, and structuring AngularJS applications, then step right in. These styles are based on my development experience with [AngularJS](//angularjs.org), presentations, [Pluralsight training courses](http://pluralsight.com/training/Authors/Details/john-papa) and working in teams. @@ -2329,6 +2329,88 @@ Unit testing helps maintain clean code, as such I included some of my recommenda *Why?*: Sinon makes it easier to toggle between Jasmine and Mocha, if you want to try both. +### Stubbing and Matching Promises +###### [Style [C001](#style-c001)] + + - Use [jasmine promise spy strategies](assets/jasmine-promise-spy-strategies.js?raw=true) along with [jasmine promise matchers](https://github.com/bvaughn/jasmine-promise-matchers). + + *Why?*: To avoid cluttering and improve readability of your tests. + + ```javascript + // src/main/calculator/direct/security/authentication.js + angular + .module('calculator.direct.security', []) + .factory('authentication', authenticationFactory); + + authenticationFactory.$inject = ['$http']; + + function authenticationFactory($http) { + var service = { + getCurrentUser: getCurrentUser + }; + + /** + * @name getCurrentUser + * @desc Returns currently authenticated user. + * @returns {Object.} resolved to object representing authenticated user or null + */ + function getCurrentUser() { + return $http.get('/authentication/current-user').then(function (response) { + // NB promise chaining in action + return response.data; + }); + } + + return service; + } + ``` + + ```javascript + // src/test/calculator/direct/security/authentication.spec.js + describe('authentication service', function () { + + var $rootScope, $httpBackend, $q, authentication; + + beforeEach(function () { + module('calculator.direct.security'); + inject(function (_$rootScope_, _$httpBackend_, _$q_, _authentication_) { + $rootScope = _$rootScope_; + $httpBackend = _$httpBackend_; + $q = _$q_; + authentication = _authentication_; + }); + }); + + it('should return currently authenticated user', function () { + $httpBackend.whenGET('/authentication/current-user') + .respond({id: 123, email: 'mtyson@lu.pl'}); + + expect(authentication.getCurrentUser()) + .toBeResolvedWith({id: 123, email: 'mtyson@lu.pl'}); + + $httpBackend.flush(); + $rootScope.$digest(); // actually resolve promise(s) + }); + + it('should stub/mock returned promise', function () { + spyOn(authentication, 'getCurrentUser').and + .returnResolvedPromise($q, {id: 123, email: 'mtyson@lu.pl'}); + + expect(authentication.getCurrentUser()) + .toBeResolvedWith({id: 123, email: 'mtyson@lu.pl'}); + + $rootScope.$digest(); // actually resolve promise(s) + }); + + }); + + ``` + + - Note: The `toBeResolvedWith` matcher provided by [jasmine promise matchers](https://github.com/bvaughn/jasmine-promise-matchers), + and the `returnResolvedPromise` spy strategy defined by [jasmine promise spy strategies](assets/jasmine-promise-spy-strategies.js?raw=true). + + - Note: Similarly, you can use the `toBeRejectedWith` matcher as well as the `returnRejectedPromise` spy strategy. + ### Headless Browser ###### [Style [Y194](#style-y194)] diff --git a/assets/jasmine-promise-spy-strategies.js b/assets/jasmine-promise-spy-strategies.js new file mode 100644 index 00000000..3b3f5f35 --- /dev/null +++ b/assets/jasmine-promise-spy-strategies.js @@ -0,0 +1,20 @@ +'use strict'; + +beforeEach(function () { + + jasmine.SpyStrategy.prototype.returnResolvedPromise = function (promiseApi, resolvedValue) { + return this.callFake(function () { + var deferred = promiseApi.defer(); + deferred.resolve(resolvedValue); + return deferred.promise; + }); + }; + + jasmine.SpyStrategy.prototype.returnRejectedPromise = function (promiseApi, rejectValue) { + return this.callFake(function () { + var deferred = promiseApi.defer(); + deferred.reject(rejectValue); + return deferred.promise; + }); + }; +});