Promises with $q and angular in karma, mocha & cha

2019-08-13 11:08发布

问题:

So i tried to get the promises to work in my angular app tests, can anyone figure out what im doing wrong here it keeps returning:

Error: timeout of 2000ms exceeded. Ensure the done() callback is being called in this test.

I don't know if it's $q.

FYI i also tried the it('test', function(done){... done();})

Controller

(function() {
    'use strict';

    angular
        .module('controller.editor', [])
        .controller('EditorController', EditorController);

    function EditorController($scope, $q) {
        var vm = this;

        angular.extend(vm, {
            hack: hack
        });

        function hack(bool) {
            return $q(function(resolve, reject) {
                if (bool) {
                    resolve(true);
                }

                reject(false);
            });
        }
    }
});

Test

describe('EditorController', function() {
    var vm, scope, $controller, $rootScope, $injector;

    beforeEach(function() {
        module('app');

        //Inject
        inject(function(_$injector_) {
            $injector = _$injector_;
            $controller = $injector.get('$controller');
            $rootScope = $injector.get('$rootScope');

            // Create new scope object
            scope = $rootScope.$new();

            // Bind the controller
            vm = $controller('EditorController', {
                $scope: scope
            });
        });
    });

    describe('#addCustom', function() {
        it('test', function(done) {
            var aHack = vm.hack(true);

            aHack.then(function(bool){
                // Resolve
                expect(bool).to.be.eq(true);
                done();
            }, function() {
                // Reject
                expect(bool).to.be.eq(false);
                done();
            });
        });
    });
});

回答1:

When testing promises in angular, it's a best practice to depend on angular machinery to do its job to resolve promises synchronously instead of asynchronously.

This makes the code easier to read and maintain.

It's also less error prone; doing assertions in .then() is an anti-pattern because if the callback is never called, your assertions will never run.

To use the angular way of testing, you should:

  1. remove done
  2. do $rootScope.$digest() in the test to resolve promises
  3. do your assertions

Applying this to your code would be:

describe('#addCustom', function() {
    it('test', function() {
        var __bool = false;
        var aHack = vm.hack(true).then(function(bool) {
            __bool = bool;
        });

        $rootScope.$digest();

        expect(__bool).to.be.eq(true);
    });
});

But it's tricky, because $rootScope.$digest resolves only $q promises, not all promises, particularly not the promises created via Promise() constructor from various es5 shims, see this:

Promise resolved too late in angularjs jasmine test

See also:

http://brianmcd.com/2014/03/27/a-tip-for-angular-unit-tests-with-promises.html



回答2:

The problem is that your Promise is resolved before you setup your 'then' behavior.

Take a look at these examples that all use a setTimeout.