I am looking to test a JavaScript method that I have written. Here is a simplified pseudo code representation of it:
$scope.savePerson = function(){
Person.create($scope.person).then(function(newPerson){
if($scope.person.org){
Organization.addPerson($scope.person.org, newPerson.id);
}
toaster.pop('success', "person added");
}
Although this sample uses restangular, the concept behind it is straightforward. The savePerson method saves the person object first and then attempts to save the person to an organization if an organization is specified. It is important that the organization cannot be saved until the person has been.
To test this logic, I plan on creating spies for both Person and Organization, and then verifying that they are both being called. A common approach may resemble the following:
describe( 'save person', function () {
var Person, Organization, person;
beforeEach(inject(function($controller, $q, _Organization_, _Person_) {
Person=_Person_;
Organization=_Organization_;
person={...} //whatever data is needed, including an organization
Person.create=jasmine.createSpy('create()').andCallFake( funciton(newPerson){
var defer = $q.defer();
defer.resolve(personParam);
return defer.promise;
});
Organization.addPerson= jasmine.createSpy('addPerson()').andCallFake(funciton(personParam){
var defer=$q.defer();
defer.resolve(personParam);
return defer.promise;
});
controllerOptions.Person=Person;
controllerOptions.Organization=Organization;
controllerOptions.person=person;
MyController= $controller('MyController', controllerOptions);
}));
it('adds org if exists', function(){
$scope.savePerson($scope.person);
expect(Person.create).toHaveBeenCalled();
expect(Organization.addPerson).toHaveBeenCalled();
});
However, currently this will fail, specifically because addPerson will not be called. This is not a flaw in the code itself but rather an issue with threading. Due to using .then on Person.create, an asynchronous thread is created. As a result, Organization.addPerson will not execute until the async thread triggers and runs the then logic. The problem lies in the fact that the test proceeds without waiting for that thread to complete, leading to failures when reaching the expectations.
One quick but lazy solution would involve adding a short 10 millisecond wait in the test. By doing so, the async thread will start running immediately upon waiting, allowing addPerson to be executed promptly. However, relying on timing assumptions is not rigorous enough for testing purposes.
An alternative approach could involve having the organization's addPerson function set some "addPersonCalled" value and then utilizing async calls that only run once addPersonCalled is triggered, though this may appear cumbersome.
This dilemma seems like a common scenario. I am curious if Jasmine offers a more elegant solution for handling such cases? Is there a way to instruct Jasmine to wait for any current .then methods invoked on promises to resolve before proceeding with a test?