3

i have some issues when i try to test controller which uses promises with jasmine.

I have following jasmine test:

describe('Testing a controller', function () {
    var $scope, $root, ctrl, $q, deferred;
    var MockService;

    beforeEach(function () {
        module('myApp');
        inject(function ($rootScope, $controller, $q) {

            $root = $rootScope;
            $scope = $rootScope.$new();

            MockService = {
                fetch: function () {
                    return {
                        get: function () {
                            deferred = $q.defer();
                            return {
                                $promise: deferred.promise
                            };
                        }
                    };
                }
            };

            ctrl = $controller('myController', {
                $scope: $scope,
                $root: $rootScope,
                MyService: MockService,
            });
        });

        spyOn(MockService, 'fetch').andCallThrough();
    });

    it('Get data from promise', function () {

        $scope.init();
        deferred.resolve("It worked!");
        $root.$digest();
        expect(MockService.fetch).toHaveBeenCalled();
        expect($scope.test).toBe('It worked!');
    });

});

and this is the controller:

controllers.controller('myController', ['$scope','MyService'
    function ($scope, myService) {
        $scope.init = function () {
            myService.fetch().get().$promise.then(function (data) {
                $scope.test = data;
            }, function (error) {
                $scope.error = error;
            });
        };
    }
]);

And the test keeps failing: "Expected undefined to be 'It Worked!'."

It seems that the promise gets resolved and "then" function is never called. Any ideas what's the issue ?

2 Answers 2

2

The code as you've presented above seems to work (now that @dfsq's fix has been applied).

I've only been able to reproduce the error you're experiencing by commenting out either of the following lines:

deferred.resolve("It worked!");
$root.$digest();

... which makes me wonder if in your actual code (not the sample presented above) there is something wrong with the implementation of the equivalents of the above two lines of code.

Have you, perhaps, earlier in the same test done a deferred.reject(), or deferred.resolve() without passing a parameter?

A demo of your jasmine test using the HTMLReporter:

/* Angular App */
(function() {
  "use strict";
  angular.module('myApp', [])
    .controller('myController', ['$scope', 'MyService',
      function($scope, myService) {
        $scope.init = function() {
          myService.fetch().get().$promise.then(function(data) {
            $scope.test = data;
          }, function(error) {
            $scope.error = error;
          });
        };
      }
    ]);
})();

/* Unit Test */
(function() {
  "use strict";
  var consoleLog = document.querySelector('#log');

  describe('Testing a controller', function() {
    var $scope, $root, ctrl, $q, deferred;
    var MockService;

    beforeEach(function() {
      module('myApp');
      inject(function($rootScope, $controller, $q) {

        $root = $rootScope;
        $scope = $rootScope.$new();

        MockService = {
          fetch: function() {
            return {
              get: function() {
                deferred = $q.defer();
                return {
                  $promise: deferred.promise
                };
              }
            };
          }
        };

        ctrl = $controller('myController', {
          $scope: $scope,
          $root: $rootScope,
          MyService: MockService,
        });
      });

      spyOn(MockService, 'fetch').andCallThrough();
    });

    it('Get data from promise', function() {
      consoleLog.innerHTML += "<p>" + [
        "START OF TEST:",
        "$scope.test: " + $scope.test,
        "$scope.error: " + $scope.error
      ].join("<br />") + "</p>";

      $scope.init();
      deferred.resolve("It worked!");
      $root.$digest();
      expect(MockService.fetch).toHaveBeenCalled();
      expect($scope.test).toBe('It worked!');

      consoleLog.innerHTML += "<p>" + [
        "END OF TEST:",
        "$scope.test: " + $scope.test,
        "$scope.error: " + $scope.error
      ].join("<br />") + "</p>";
    });

  });
})();


/* Jasmine Bootstrap */
(function() {
  "use strict";

  var jasmineEnv = jasmine.getEnv(),
    htmlReporter = new jasmine.HtmlReporter();

  jasmineEnv.addReporter(htmlReporter);
  jasmineEnv.execute();

})();
<link rel="stylesheet" href="//cdn.jsdelivr.net/jasmine/1.3.1/jasmine.css" />
<script src="//cdn.jsdelivr.net/jasmine/1.3.1/jasmine.js"></script>
<script src="//cdn.jsdelivr.net/jasmine/1.3.1/jasmine-html.js"></script>
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.2.28/angular.min.js"></script>
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.2.28/angular-mocks.js"></script>

<pre id="log"></pre>

Sign up to request clarification or add additional context in comments.

1 Comment

Thank you, this helped. I had a simple mistake in the code which prevented it from working.
0

Looks like a simple problem: controller expects myService by you are injecting completely another mock service MyService (note "M"). As the result mock is never used but rather original one, which never resolves promise as you expect.

It should be:

ctrl = $controller('myController', {
    $scope: $scope,
    $root: $rootScope,
    myService: MockService,
});

1 Comment

My bad, corrected that mistake but the problem still persists.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.