Unit test service that returns promise Angularjs Jasmine
To mock a function that returns a promise, it will need to also return a promise, which then needs to be resolved as a separate step.
In your case the deferred.resolve()
you pass to the spy needs to be replaced with deferred.promise
, and the deferred.resolve() performed separately.
beforeEach(function() {
var deferred = $q.defer();
spyOn(dialogFactory, "confirmDeleteDialog").and.returnValue(deferred.promise);
spyOn(requestNotificationChannel, "deleteMessage");
$scope.deleteMessage(5);
deferred.resolve();
$rootScope.$digest();
});
it("delete message notification is called", function() {
expect(requestNotificationChannel.deleteMessage).toHaveBeenCalled();
});
I suspect you also need to call $rootScope.$digest()
, as Angular's promise implementation is tied to the digest loop.
Also, slightly unrelated to your question, but I don't think you need to create your own deferred object in confirmDeleteDialog
. The (anti) pattern you're using has been labelled 'The Forgotten Promise', as in http://taoofcode.net/promise-anti-patterns/
When is simpler, uses less code, and I think that allows better error handling, you can just return the promise that the $modal
service creates:
var modalInstance = $modal.open({...});
return modalInstance.result;
If you want to modify what the calling function sees, in terms of resolved/rejected values, you can create a chained promise by returning the result of then
:
var modalInstance = $modal.open({...});
return modalInstance.result.then(function(successResult) {
return 'My other success result';
}, function(failureReason) {
return $q.reject('My other failure reason');
});
You would usually want to do this if you don't want to expose the inner-workings of a function to its caller. This is analogous to the concept of re-throwing an exception in synchronous programming.
Related videos on Youtube
Mdb
Updated on April 07, 2020Comments
-
Mdb over 4 years
EDITED per Michal Charemza post.
I have a service that represents angularui modal dialog:
app.factory("dialogFactory", function($modal, $window, $q) { function confirmDeleteDialog() { var modalInstance = $modal.open({ templateUrl: "../application/factories/confirmDeleteDialog.htm", controller: function($scope, $modalInstance) { $scope.ok = function() { $modalInstance.close("true"); }; $scope.cancel = function() { $modalInstance.dismiss("false"); }; } }); return modalInstance.result.then(function(response) { return 'My other success result'; }, function(response) { return $q.reject('My other failure reason'); }); }; return { confirmDeleteDialog: confirmDeleteDialog }; });
On calling the delete method if the user has clicked Ok from the dialog
requestNotificationChannel.deleteMessage(id)
is executed.$scope.deleteMessage = function(id) { var result = dialogFactory.confirmDeleteDialog(); result.then(function(response) { requestNotificationChannel.deleteMessage(id); }); };
The problem is I am not able to unit test this.
This is my test. I have correctly injected the q service but I am not sure what should I return from
"confirmDeleteDialog"
spy...describe("has a delete method that should call delete message notification", function() { var deferred = $q.defer(); spyOn(dialogFactory, "confirmDeleteDialog").and.returnValue(deferred.promise); spyOn(requestNotificationChannel, "deleteMessage"); $scope.deleteMessage(5); deferred.resolve(); it("delete message notification is called", function() { expect(requestNotificationChannel.deleteMessage).toHaveBeenCalled(); }); });
But I am receiving
expected spy deleteMessage to have been called
. Which means that theresult.then
... part is not executed. What am I missing ? -
Michal Charemza over 10 yearsI've added a part about
$rootScope.$digest()
-
Benjamin Gruenbaum over 10 yearsYes,
$digest()
is definitely required here.