Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Unit test service that returns promise Angularjs Jasmine

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 the result.then... part is not executed. What am I missing ?

like image 334
Mdb Avatar asked Apr 07 '14 07:04

Mdb


People also ask

Is Jasmine used for unit testing?

Jasmine is a framework for unit testing JavaScript. It's open source and has more than 15,000 stars on GitHub at the time of this writing. Its popularity among developers means that it has a strong community and documentation available when you get stuck.

What is promise in Jasmine?

Promises. If you need more control, you can explicitly return a promise instead. Jasmine considers any object with a then method to be a promise, so you can use either the Javascript runtime's built-in Promise type or a library.

What is Jasmine testing framework and how do you use it for Angular unit testing?

Jasmine is a behavior development testing framework. Unit tests are written using Jasmine and are run to see if individual parts of an application are working correctly. As a result, unit tests will either pass or fail depending on if the code is working correctly or has a bug.

What is Jasmine in Angular testing?

Angular ships with Jasmine, a JavaScript framework that enables you to write and execute unit and integration tests. Jasmine consists of three important parts: A library with classes and functions for constructing tests. A test execution engine. A reporting engine that outputs test results in different formats.


1 Answers

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.

like image 120
Michal Charemza Avatar answered Oct 06 '22 07:10

Michal Charemza