Last active
August 8, 2018 15:24
-
-
Save ThomasBurleson/6334259 to your computer and use it in GitHub Desktop.
When using Karma, RequireJS, and AngularJS... demonstrate how Promise(s) callbacks are not auto-triggered when testing with Karma/Jasmine tests.
Here we use a Controller and PeopleService with an API that returns Promises. While our mock service resolves the promise, we did that in the context of our test which is `outside` the AngularJS world. …
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
(function( describe ){ | |
"use strict"; | |
/** | |
* Demonstration of how to properly use Jasmine testing with AngularJS promise-based services. | |
* | |
* NOTE: based on the origina version from Jim Lavin's | |
* http://codingsmackdown.tv/blog/2012/12/28/mocking-promises-in-unit-tests/ | |
* | |
* FIXED: Igor Minar fixed this on 8/22/2013. Check with v1.2.x master branch of AngularJS | |
* https://github.com/IgorMinar/angular.js/commit/8bc169839243728b7dc9279fa31371824efb7afc | |
*/ | |
define( function() | |
{ | |
/** | |
* Typical NG Controller with | |
* | |
* @param $scope Injected Presentation Model | |
* @param delegate Injected UserService w/ async responses | |
* | |
* @constructor | |
*/ | |
var PeopleController = function ($scope, delegate) | |
{ | |
var onLoadAll = function() { | |
return delegate | |
.loadUsers() | |
.then(function( list ) | |
{ | |
$scope.knownUsers = list; | |
}); | |
}; | |
// Configure the PM | |
$scope.knownUsers = []; | |
$scope.loadPeople = onLoadAll; | |
}; | |
describe( 'TestSuite for PeopleController', function() | |
{ | |
var pScope = null, | |
delegate = null, | |
/** | |
* Simulation of button click which triggers PeopleController::loadPeople() | |
*/ | |
loadPeople = function( ) | |
{ | |
/** | |
* | |
* Simulate a button click to trigger loadPeople() | |
* | |
* Redirect to the Controller's scope and FLUSH the digest() | |
* to resolve promises... | |
* | |
* While we resolved the promise, we did that in the context of | |
* our test which is `outside` the AngularJS world. So we have to call digest() | |
* on the scope’s root to have AngularJS flush the pending deferreds and invoke | |
* the then() clause in our controller. Once this happens the code in the then() | |
* clause of our controller will be executed | |
* | |
*/ | |
pScope.loadPeople(); | |
pScope.$root.$digest(); | |
}; | |
// ****************************************************** | |
// Setup/TearDown Methods | |
// ****************************************************** | |
beforeEach( inject( function( $rootScope, $q, $controller ) | |
{ | |
var dfd = $q.defer(), | |
scope = $rootScope.$new(), | |
/** | |
* Mock UserService with internal model and async loadUsers() | |
* @type {{registry: Array, loadUsers: Function}} | |
*/ | |
userService = { | |
registry : [ | |
{ | |
FirstName : "Jim", | |
LastName : "Lavin", | |
Email : "jlavin@jimlavin.net", | |
Bio : "Creator and Host of Coding Smackdown TV" | |
}, | |
{ | |
FirstName : "Thomas", | |
LastName : "Burleson", | |
Email : "ThomasBurleson@gmail.com", | |
Bio : "Web Solutions Architect and AngularJS Ninja" | |
} | |
], | |
/** | |
* Mock `loadUsers()` returns `registry` | |
* @returns {Function|promise} | |
*/ | |
loadUsers : function() | |
{ | |
dfd.resolve( userService.registry ); | |
return dfd.promise; | |
} | |
}; | |
// Build controller instance and then | |
// Configure variables for the subsequent tests | |
$controller( | |
PeopleController, | |
{ | |
$scope : scope, | |
delegate: userService | |
} | |
); | |
delegate = userService; | |
pScope = scope; | |
})); | |
// ****************************************************** | |
// loadPeople() Tests | |
// ****************************************************** | |
/** | |
* | |
*/ | |
it('should call loadUsers() on the UserService when loadPeople() is called', function() | |
{ | |
spyOn(delegate, 'loadUsers').andCallThrough(); | |
loadPeople(); | |
expect(delegate.loadUsers).toHaveBeenCalled(); | |
}); | |
/** | |
* | |
*/ | |
it('should populate the knownUsers when loadPeople() is called', function() | |
{ | |
loadPeople(); | |
expect( pScope.knownUsers.length ).toBe(2); | |
}); | |
}); | |
}); | |
})( describe ); | |
(function( describe ){
"use strict";
/**
* Demonstration of how to properly use Jasmine testing with AngularJS promise-based services.
*
* NOTE: based on the origina version from Jim Lavin's
* http://codingsmackdown.tv/blog/2012/12/28/mocking-promises-in-unit-tests/
*
* FIXED: Igor Minar fixed this on 8/22/2013. Check with v1.2.x master branch of AngularJS
* https://github.com/IgorMinar/angular.js/commit/8bc169839243728b7dc9279fa31371824efb7afc
*/
define( function()
{
/**
* Typical NG Controller with
*
* @param $scope Injected Presentation Model
* @param delegate Injected UserService w/ async responses
*
* @constructor
*/
var PeopleController = function ($scope, delegate)
{
var onLoadAll = function() {
return delegate
.loadUsers()
.then(function( list )
{
$scope.knownUsers = list;
});
};
// Configure the PM
$scope.knownUsers = [];
$scope.loadPeople = onLoadAll;
};
describe( 'TestSuite for PeopleController', function()
{
var proxy = null,
delegate = null;
// ******************************************************
// Setup/TearDown Methods
// ******************************************************
beforeEach( inject( function( $rootScope, $q, $controller )
{
var dfd = $q.defer(),
scope = $rootScope.$new(),
/**
* Mock UserService with internal model and async loadUsers()
* @type {{registry: Array, loadUsers: Function}}
*/
userService = {
registry : [
{
FirstName : "Jim",
LastName : "Lavin",
Email : "jlavin@jimlavin.net",
Bio : "Creator and Host of Coding Smackdown TV"
},
{
FirstName : "Thomas",
LastName : "Burleson",
Email : "ThomasBurleson@gmail.com",
Bio : "Web Solutions Architect and AngularJS Ninja"
}
],
/**
* Mock `loadUsers()` returns `registry`
* @returns {Function|promise}
*/
loadUsers : function()
{
dfd.resolve( userService.registry );
return dfd.promise;
}
};
// Build controller instance and then
// Configure variables for the subsequent tests
$controller(
PeopleController,
{
$scope : scope,
delegate: userService
}
);
delegate = userService;
proxy = scope;
}));
// ******************************************************
// loadPeople() Tests
// ******************************************************
/**
*
*/
it('should call loadUsers() on the UserService when loadPeople() is called', function()
{
spyOn(delegate, 'loadUsers').andCallThrough();
proxy.loadPeople();
expect(delegate.loadUsers).toHaveBeenCalled();
});
/**
*
*/
it('should populate the knownUsers when loadPeople() is called', function()
{
proxy.loadPeople()
.then( function onResult_loadPeople( knownUsers )
{
expect( knownUsers ) .toBe( proxy.knownUsers );
expect( knownUsers.length ) .toBe(2);
expect( proxy.knownUsers.length ).toBe(2);
});
});
});
});
})( describe );
The above usage of expect()
assertions within a promise resolve handler may NOT work properly from Jasmine testing perspectives. Jasmine may have already assumed the test has completed.
Further research is needed here...
Stay tuned for Jasmine-as-Promised extension that will leverage runs( <promise> )
to block test completion and auto-complete when promise resolves/rejects.
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Here is a revised version using the changes submitted by IgorMinar in http://bit.ly/159KY0z
Also includes improvements to using Promises with
expect( ... )