You are here probably because you are tired of writing verbose specs for AngularJS projects. Or because you feel that projects using UI-Router besides of the fact of being awesome are also difficult to test. Or both!.
JAngular (alternatively jangular-matchers) is set of Jasmine matchers for AngularJS and UI-Router
that makes developers life easier.
Sources are completely written in Coffeescript, however it can be successfully used in Javascript and samples are provided as well in Javascript.
coffee/: sample AngularJS Coffeescript code, module, controller, service, UI-Router state configuration.dist/: packaged Javascript code to be included in your specs (see samples andkarma.conf.js).js/: sample AngularJS Javascript code, module, controller, service, UI-Router state configuration.spec_coffee: sample Coffeescript specs testing code located atcoffeedirectory.spec_js: sample Javascript specs testing code located atjsdirectory.src: Coffeescript source code of Jasmine matchers.coffeelint.json: coffee lint configuration.gulpfile.js: gulp tasks for building JAngular if you want to contribute.karma.conf.js: configuration file Karma test runner for Javascript. Users of JAngular should take a look to this file.LICENSE: license filepackage.json: npm configuration for JAngularREADME.md: this document.
$ npm install jangular-matchers
- toCall()
- toCallWith()
- toSubscribeSuccess()
- toSubscribeError()
- toSubscribe()
- toCallbackSuccessWith()
- toCallbackErrorWith()
- toBeAnState()
- toBeAbstract()
- toHaveUrl()
- toHaveController()
- toHaveControllerAlias()
- toHaveTemplate()
- toHaveTemplateUrl()
- toResolveByCalling()
- toResolveByCallingWith()
- to_have_view() TODO
Every sample Jasmine matcher for AngularJS HTTP service will be enclosed in the following describe code section:
describe('sample http service', function () { var subject; beforeEach(function () { // make the matchers available jasmine.addMatchers(jangular_matchers); // initialize module module('sample.js.module'); }); beforeEach(inject(function (sampleHttpService) { subject = sampleHttpService; })); afterEach(inject(function ($httpBackend) { $httpBackend.verifyNoOutstandingExpectation(); $httpBackend.verifyNoOutstandingRequest(); })); it('GETs the given URI', function(){ expect(subject.doGet).toBareGet('/data'); }); });
describe 'sample http service matchers', -> # make the matchers available beforeEach -> jasmine.addMatchers jangular_matchers # initialize module beforeEach -> module 'sample.coffee.module' # inject the http service (SUT) beforeEach inject (sampleHttpService) => @subject = sampleHttpService # after every test assert for no pending expectations & requests afterEach inject ($httpBackend) -> $httpBackend.verifyNoOutstandingExpectation() $httpBackend.verifyNoOutstandingRequest() # (every test will be placed here) it 'example spec', => expect(true).toEqual true
Every sample AngularJS HTTP service operation will be enclosed in the following code block:
function sampleHttpService($http) { return { someFunction: function() { $http.get('/some_url'); } }; }; angular.module('sample.js.module').factory('sampleHttpService', ['$http', sampleHttpService]);
sampleHttpService = ($http) -> class SampleHttpService # (operations will be listed here) example_operation: -> $http.get '/some_uri' new SampleHttpService angular.module('sample.coffee.module').factory 'sampleHttpService', ['$http', sampleHttpService]
Ensures that the service operation issues a GET to a given URI and unwraps the response body on success. Expects the service operation under test to return the GET promise as well.
it('GETs a given URI and unwraps the response', function(){ expect(subject.doGetAndUnwrap).toGet('/data'); });
doGetAndUnwrap: function() { return $http.get('/data').then(function(response){ return response.data; }); }
it 'GETs a given URI and unwraps the response', => expect(@subject.do_get_and_unwrap).to_get '/data'
do_get_and_unwrap: -> $http.get('/data').then (reply) -> reply.data
Ensures that the service operation issues a GET to a given URI.
it('GETs the given URI', function(){ expect(subject.doGet).toBareGet('/data'); });
doGet: function() { $http.get('/data'); }
it 'GETs a given URI', => expect(@subject.do_get).to_bare_get '/data'
do_get: -> $http.get '/data'
Ensures that the service operation unwraps the response body on success. Expects the service operation under test to return the GET promise as well.
it('GET unwraps the response', function() { expect(subject.doUnwrap).toUnwrapGet(); });
doUnwrap: function() { return $http.get('/any_path').then(function(response) { return response.data; }); }
it 'GETs unwraps the response', => expect(@subject.do_get_and_unwrap).to_unwrap_get()
do_unwrap: -> $http.get('/any_path').then (reply) -> reply.data
Ensures that the service operation issues a POST to a given URI and unwraps the response body on success. Expects the service operation under test to return the POST promise as well.
it('POST a given URI and unwraps the response', function() { expect(subject.doPostAndUnwrap).toPost('/post', { firstname: 'Olivia', lastname: 'Lago' }); });
doPostAndUnwrap: function () { var payload = { firstname: 'Olivia', lastname: 'Lago' }; return $http.post('/post', payload).then(function (response) { return response.data; }); }
it 'POST a given URI and unwraps the response', => expect(@subject.do_post_and_unwrap).to_post '/post', firstname: 'Olivia', lastname: 'Lago'
do_post_and_unwrap: -> $http.post('/post', firstname: 'Olivia', lastname: 'Lago').then (reply) -> reply.data
Ensures that the service operation issues a POST to a given URI.
it('POSTs a given URI', function() { expect(subject.doPost).toBarePost('/post', { firstname: 'Olivia', lastname: 'Lago' }); });
doPost: function() { $http.post('/post', { firstname: 'Olivia', lastname: 'Lago' }); }
it 'POSTs a given URI', => expect(@subject.do_post).to_bare_post '/post', firstname: 'Olivia', lastname: 'Lago'
do_post: -> $http.post '/post', firstname: 'Olivia', lastname: 'Lago'
Ensures that the service operation unwraps the response body on success. Expects the service operation under test to return the POST promise as well.
it('POST unwraps the response', function(){ expect(subject.doPostAndUnwrap).toUnwrapPost(); });
doPostAndUnwrap: function() { return $http.post('/post', {}).then(function(response) { return response.data; }); }
it 'POST unwraps the response', => expect(@subject.do_post_and_unwrap).to_unwrap_post()
do_post_and_unwrap: -> $http.post('/post', firstname: 'Olivia', lastname: 'Lago').then (reply) -> reply.data
Ensures that the service operation issues a PUT to a given URI and unwraps the response body on success. Expects the service operation under test to return the PUT promise as well.
// doPut it('PUTs to a given URI', function () { expect(subject.doPut).to_put('/put_uri', {id: 1, given_name: 'Maria', last_name: 'Juana'}); });
doPut: function () { var payload = { id: 1, given_name: 'Maria', last_name: 'Juana' }; return $http.put('/put_uri', payload).then(function (response) { return response.data; }); }
# to_put it 'PUTs to a given URI', => expect(@subject.do_put).to_put '/put_uri', id: 1, given_name: 'Maria', last_name: 'Juana'
do_put: -> $http.put('/put_uri', id: 1, given_name: 'Maria', last_name: 'Juana').then (reply) -> reply.data
Ensures that the service operation issues a DELETE to a given URI and unwraps the response body on success. Expects the service operation under test to return the DELETE promise as well.
// doDelete it('DELETEs to a given URI', function () { expect(subject.doDelete).toDelete('/customer/1'); });
doDelete: function () { return $http.delete('/customer/1').then(function (response) { return response.data; }); }
# to_delete it 'DELETEs to a given URI', => expect(@subject.do_delete).to_delete '/customer/1'
do_delete: -> $http.delete('/customer/1').then (reply) -> reply.data
These matchers are not exclusively for AngularJS controllers, they may be used in other AngularJS services as well. Every sample Jasmine matcher for AngularJS controller will be enclosed in the following describe code section:
describe('sample Javascript controller matchers', function () { var subject; var sampleHttpService; beforeEach(function () { // make matchers available jasmine.addMatchers(jangular_matchers); // initialize module module('sample.js.module'); }); // create controller an inject dependencies beforeEach(inject(function ($controller, _sampleHttpService_) { subject = $controller('SampleController'); sampleHttpService = _sampleHttpService_ })); it('is defined', function () { expect(subject).toBeDefined(); }); // more specs listed here! })
describe 'sample controller matchers', -> # make the matchers available beforeEach -> jasmine.addMatchers jangular_matchers # initialize module beforeEach -> module 'sample.coffee.module' # create controller and inject dependencies beforeEach inject ($controller, sampleHttpService) => @subject = $controller 'SampleController' @sampleHttpService = sampleHttpService # (example specs listed here) it 'example spec', => expect(true).toEqual true
Every sample AngularJS controller operation will be enclosed in the following block:
SampleController = function (sampleHttpService) { this.someSampleOperation = function () { console.log('Hello I am an AngularJS controller!'); }; // more operations listed here! }; angular.module('sample.js.module').controller('SampleController', ['sampleHttpService', SampleController]);
class SampleController constructor: (@sampleHttpService) -> # (sample operations listed here) some_sample_operation: => console.log 'Hello I am an AngularJS controller!' # more operations listed here
Ensures that the controller operation calls the given service operation without arguments.
// toCall it('calls a service', function () { expect(subject.doServiceCall).toCall(sampleHttpService, 'doGet'); }); `` ##### impl ``` Javascript this.doServiceCall = function () { sampleHttpService.doGet(); };
it 'calls a service', => expect(@subject.do_service_call).to_call @sampleHttpService, 'do_get'
do_service_call: => @sampleHttpService.do_get()
Ensures that the controller operation calls the given service operation with the provided arguments.
// toCallWith it('calls a service with parameters', function () { expect(function () { subject.doServiceCallWithParams(1, 2, 3) }).toCallWith(sampleHttpService, 'doGetWith', 1, 2, 3); }); it('calls a service with hash parameters', function () { expect(function () { subject.doServiceCallWithHashParams({a: 1, b: 2, c: 3}) }).toCallWith(sampleHttpService, 'doGetWithHash', {x: 1, y: 2, z: 3}); });
this.doServiceCallWithParams = function (a, b, c) { sampleHttpService.doGetWith(a, b, c); }; this.doServiceCallWithHashParams = function (h) { sampleHttpService.doGetWithHash({x: h.a, y: h.b, z: h.c}); }
it 'calls a service with parameters', => expect(=> @subject.do_service_call_with_params 1, 2, 3).to_call_with @sampleHttpService, 'do_get_with', 1, 2, 3 it 'calls a service with hash parameters', => expect(=> @subject.do_service_call_with_hash_params a: 1, b: 2, c: 3).to_call_with @sampleHttpService, 'do_get_with_hash', x: 1, y: 2, z: 3
do_service_call_with_params: (a, b, c) => @sampleHttpService.do_get_with a, b, c do_service_call_with_hash_params: ({a, b, c}) => @sampleHttpService.do_get_with_hash x: a, y: b, z: c
Ensures that the controller operation subscribes to promise on success (completion) with the provided operation.
// toSubscribeSuccess it('subscribes to promise success', function(){ expect(subject.doSubscribe).toSubscribeSuccess(sampleHttpService, 'doGet', subject.doGetSuccess); });
// disambiguation of context var me = this; this.doSubscribe = function () { sampleHttpService.doGet().then(me.doGetSuccess); }; this.doGetSuccess = function() { console.log('get successfully executed'); };
it 'subscribes to promise success', => expect(@subject.do_subscribe).to_subscribe_success @sampleHttpService, 'do_get', @subject.do_get_success
do_subscribe: => @sampleHttpService.do_get().then @do_get_success do_get_success: ->
Ensures that the controller operation subscribes to promise on failure (rejection) with the provided operation.
// toSubscribeError it('subscribes to promise error', function () { expect(subject.doSubscribeToError).toSubscribeError(sampleHttpService, 'doGet', subject.doGetFails); });
// disambiguation of context var me = this; this.doSubscribeToError = function () { sampleHttpService.doGet().then(function () { }, me.doGetFails); }; this.doGetFails = function () { console.log('the get that failed you!'); }
it 'subscribes to promise error', => expect(@subject.do_subscribe_to_error).to_subscribe_error @sampleHttpService, 'do_get', @subject.do_get_fails
do_subscribe_to_error: => @sampleHttpService.do_get().then (->), @do_get_fails do_get_fails: ->
Ensures that the controller operation subscribes to promise on success (completion) and failure (rejection) at the same time with the provided operations.
// toSubscribe it('subscribes to success & error', function(){ expect(subject.doFullSubscribe).toSubscribe(sampleHttpService, 'doGet', subject.doGetSuccess, subject.doGetFails); });
var me = this; this.doFullSubscribe = function () { sampleHttpService.doGet().then(me.doGetSuccess, me.doGetFails); }; this.doGetSuccess = function () { console.log('get successfully executed'); }; this.doGetFails = function () { console.log('the get that failed you!'); }
it 'subscribes to success & error', => expect(@subject.do_full_subscribe).to_subscribe @sampleHttpService, 'do_get', @subject.do_get_success, @subject.do_get_fails
do_full_subscribe: => @sampleHttpService.do_get().then @do_get_success, @do_get_fails do_get_success: -> do_get_fails: ->
Ensures that the controller operation callbacks the provided operation directly or indirectly when then promise success (completion). The difference between to_subscribe_success() and to_subscribe() with respect to to_callback_success_with() is the indirection level. to_callback_success_with() allows indirect calls, so is more flexible. The with suffix demands for arguments during the callback.
// toCallbackSuccessWith it('callbacks the function when promise success with given parameters', function(){ expect(subject.doCallback).toCallbackSuccessWith(sampleHttpService, 'doGet', subject, 'doGetSuccessWith', 1, 2, 3); });
var me = this; this.doCallback = function () { sampleHttpService.doGet().then(function () { me.doGetSuccessWith(1, 2, 3); }); }; this.doGetSuccessWith = function (a, b, c) { console.log('calling back with parameters'); };
it 'callbacks the function when promise success with given parameters', => expect(@subject.do_callback).to_callback_success_with @sampleHttpService, 'do_get', @subject, 'do_get_success_with', 1, 2, 3
Notice the indirection on the subscription using an anonymous function that calls the expected operation:
do_callback: => @sampleHttpService.do_get().then => @do_get_success_with(1, 2, 3) do_get_success_with: =>
Ensures that the controller operation callbacks the provided operation directly or indirectly when then promise fails (rejection). The difference between to_subscribe_error() and to_subscribe() with respect to to_callback_error_with() is the indirection level. to_callback_error_with() allows indirect calls, so is more flexible. The with suffix demands for arguments during the callback.
// toCallbackErrorWith it('callbacks the function when promise fails with given parameters', function() { expect(subject.doFailingCallback).toCallbackErrorWith(sampleHttpService, 'doGet', subject, 'doGetFailsWith', 1, 2, 3); });
var me = this; this.doFailingCallback = function () { sampleHttpService.doGet().then(function () { }, function () { me.doGetFailsWith(1, 2, 3); }); }; this.doGetFailsWith = function (a, b, c) { console.log('failing back with parameters'); };
it 'callbacks the function when promise fails with given parameters', => expect(@subject.do_failing_callback).to_callback_error_with @sampleHttpService, 'do_get', @subject, 'do_get_fails_with', 1, 2, 3
Notice the indirection on the subscription using an anonymous function that calls the expected operation:
do_failing_callback: => @sampleHttpService.do_get().then (->), => @do_get_fails_with 1, 2, 3 do_get_fails_with: =>
These matchers are designed for AngularJS when it is combined with UI-Router. Every sample Jasmine matcher for UI-Router state will be enclosed in the following describe code section:
describe('sample ui.router state matchers', function () { var subject; var state; var sampleHttpService; beforeEach(function () { // make matchers available jasmine.addMatchers(jangular_matchers); // initialize module module('sample.js.module'); }); beforeEach(inject(function ($state, _sampleHttpService_) { state = $state; sampleHttpService = _sampleHttpService_; })); // some specs listed here! });
describe 'sample ui.router state matchers', -> # make the matchers available beforeEach -> jasmine.addMatchers jangular_matchers # initialize module beforeEach -> module 'sample.coffee.module' # (example specs listed here) it 'example spec', => expect(true).toEqual true
Every UI-Router state definition will be enclosed in the following config function:
var config = function($stateProvider) { // here state configuration }; angular.module('sample.js.module').config(config);
config = ($stateProvider) -> # here state configuration for the module takes places, for instance: $stateProvider.state 'some_state', {} angular.module('sample.coffee.module').config config
Ensures that actual subject is an UI-Router state object or alternatively an state name.
// toBeAnState describe('stateA', function () { // inject ui.router $state helper and get the state object beforeEach(function () { subject = state.get('stateA'); }); it('is an state, using object variant', function () { expect(subject).toBeAnState(); }); it('is an state, using string variant', function () { expect('stateA').toBeAnState(); }); });
$stateProvider.state('stateA', {});
describe 'stateA', => # inject ui.router $state helper and get the state object beforeEach inject ($state) => @subject = $state.get 'stateA' it 'is an state, using object variant', => expect(@subject).to_be_an_state() it 'is an state, using string variant', => expect('stateA').to_be_an_state()
$stateProvider.state 'stateA', {}
Ensures that actual subject is an abstract UI-Router state. It can be used in both variants: object and string with state name.
// toBeAbstract describe('stateB', function () { beforeEach(function () { subject = state.get('stateB'); }); it('is an state, using object variant', function () { expect(subject).toBeAbstract(); }); it('is an state, using string variant', function () { expect('stateB').toBeAbstract(); }); });
$stateProvider.state('stateB', { abstract: true });
describe 'stateB', => beforeEach inject ($state) => @subject = $state.get 'stateB' it 'is an abstract, using object variant', => expect(@subject).to_be_abstract() it 'is an abstract, using string variant', => expect('stateB').to_be_abstract()
$stateProvider.state 'stateB', abstract: yes
Ensures that UI-Router state has an expected URL. It can be used in both variants: object and string with state name.
// toHaveUrl describe('stateC', function () { beforeEach(function () { subject = state.get('stateC'); }); it('has an URL, using object variant', function () { expect(subject).toHaveUrl('/some_url'); }); it('has an URL, using string variant', function () { expect('stateC').toHaveUrl('/some_url'); }); });
$stateProvider.state('stateC', { url: '/some_url' });
describe 'stateC', => beforeEach inject ($state) => @subject = $state.get 'stateC' it 'has an URL, using object variant', => expect(@subject).to_have_url '/some_url' it 'has an URL, using string variant', => expect('stateC').to_have_url '/some_url'
$stateProvider.state 'stateC', url: '/some_url'
Ensures that UI-Router state has an expected controller. It can be used in both variants: object and string with state name.
describe('stateC', function () { beforeEach(function () { subject = state.get('stateC'); }); // toHaveController it('has controller, using object variant', function () { expect(subject).toHaveController('SomeUserController'); }); it('has controller, using string variant', function () { expect('stateC').toHaveController('SomeUserController'); }); });
$stateProvider.state('stateC', { url: '/some_url', controller: 'SomeUserController' });
describe 'stateC', => beforeEach inject ($state) => @subject = $state.get 'stateC' it 'has controller, using object variant', => expect(@subject).to_have_controller 'SomeUserController' it 'has controller, using string variant', => expect('stateC').to_have_controller 'SomeUserController'
$stateProvider.state 'stateC', url: '/some_url' controller: 'SomeUserController'
Ensures that UI-Router state has an expected controller alias. It can be used in both variants: object and string with state name.
describe('stateC', function () { beforeEach(function () { subject = state.get('stateC'); }); // toHaveControllerAlias / toHaveControllerAs it('has controller alias, using object variant', function(){ expect(subject).toHaveControllerAlias('suc'); }); it('has controller alias, using string variant', function(){ expect('stateC').toHaveControllerAlias('suc'); }); it('has controller alias, using object variant', function(){ expect(subject).toHaveControllerAs('suc'); }); it('has controller alias, using string variant', function(){ expect('stateC').toHaveControllerAs('suc'); }); });
$stateProvider.state('stateC', { url: '/some_url', controller: 'SomeUserController', controllerAs: 'suc' });
describe 'stateC', => beforeEach inject ($state) => @subject = $state.get 'stateC' it 'has controller alias, using object variant', => expect(@subject).to_have_controller_alias 'suc' it 'has controller alias, using string variant', => expect('stateC').to_have_controller_alias 'suc' it 'has controller alias, using object variant', => expect(@subject).to_have_controller_as 'suc' it 'has controller alias, using string variant', => expect('stateC').to_have_controller_as 'suc'
$stateProvider.state 'stateC', url: '/some_url' controller: 'SomeUserController' controllerAs: 'suc'
Ensures that UI-Router state has an expected template. It can be used in both variants: object and string with state name.
// toHaveTemplate describe('stateD', function () { beforeEach(function () { subject = state.get('stateD'); }); it('has a template, using object variant', function () { expect(subject).toHaveTemplate('<div id="some_template"></div>'); }); it('has a template, using string variant', function () { expect('stateD').toHaveTemplate('<div id="some_template"></div>'); }); });
$stateProvider.state('stateD', { template: '<div id="some_template"></div>' });
describe 'stateD', => beforeEach inject ($state) => @subject = $state.get 'stateD' it 'has a template, using object variant', => expect(@subject).to_have_template '<div id="some_template"></div>' it 'has a template, using string variant', => expect('stateD').to_have_template '<div id="some_template"></div>'
$stateProvider.state 'stateD', template: '<div id="some_template"></div>'
Ensures that UI-Router state has an expected template URL. It can be used in both variants: object and string with state name.
// toHaveTemplateUrl describe('stateE', function () { beforeEach(function () { subject = state.get('stateE'); }); it('has a template, using object variant', function () { expect(subject).toHaveTemplateUrl('/templates/footer.html'); }); it('has a template, using string variant', function () { expect('stateE').toHaveTemplateUrl('/templates/footer.html'); }); describe('some nested view', function () { beforeEach(function () { subject = state.get('stateE').views['nested_view']; }); it('view has a template Url', function() { expect(subject).toHaveTemplateUrl('/templates/views/nested.html'); }); }); });
$stateProvider.state('stateE', { templateUrl: '/templates/footer.html', views: { nested_view: { templateUrl: '/templates/views/nested.html' } } });
describe 'stateE', => beforeEach inject ($state) => @subject = $state.get 'stateE' it 'has a template URL, using object variant', => expect(@subject).to_have_template_url '/templates/footer.html' it 'has a template URL, using string variant', => expect('stateE').to_have_template_url '/templates/footer.html'
$stateProvider.state 'stateE', templateUrl: '/templates/footer.html'
Ensures that UI-Router state resolves a given promise before entering. The expected promise resolution should take place by issuing an service call without arguments.
// toResolveByCalling describe('stateF', function () { beforeEach(function () { subject = state.get('stateF'); }); it('resolves the promise by calling service without arguments', function () { expect(subject.resolve.userProfile).toResolveByCalling(sampleHttpService, 'doGet'); }); });
var resolveUserProfile = function (sampleHttpService) { return sampleHttpService.doGet(); }; $stateProvider.state('stateF', { resolve: { userProfile: ['sampleHttpService', resolveUserProfile] } });
describe 'stateF', => beforeEach inject ($state, @sampleHttpService) => @subject = $state.get 'stateF' it 'resolves the promise by calling service without arguments', => expect(@subject.resolve.user_profile).to_resolve_by_calling @sampleHttpService, 'do_get'
$stateProvider.state 'stateF', resolve: user_profile: ['sampleHttpService', (sampleHttpService) -> sampleHttpService.do_get()]
Ensures that UI-Router state resolves a given promise before entering. The expected promise resolution should take place by issuing an service call with the given arguments.
// toResolveByCallingWith describe('stateG', function () { beforeEach(function () { subject = state.get('stateG'); }); it('resolves the promise by calling service with arguments', function () { expect(subject.resolve.userHistory).toResolveByCallingWith(sampleHttpService, 'doGetWith', 1, 'a', true); }); });
var resolveUserHistory = function (sampleHttpService) { return sampleHttpService.doGetWith(1, 'a', true); }; $stateProvider.state('stateG', { resolve: { userHistory: ['sampleHttpService', resolveUserHistory] } });
describe 'stateG', => beforeEach inject ($state, @sampleHttpService) => @subject = $state.get 'stateG' it 'resolves the promise by calling service with arguments', => expect(@subject.resolve.user_history).to_resolve_by_calling_with @sampleHttpService, 'do_get_with', 1, 'a', true
$stateProvider.state 'stateG', resolve: user_history: ['sampleHttpService', (sampleHttpService) -> sampleHttpService.do_get_with 1, 'a', true]