用承诺模拟服务,以便在另一个服务中进行测试

Mocking a service with a promise, for testing within another service

我有以下服务(为简单起见减少了):

angular.module('myApp')
    .factory('BasicTimerService', function BasicTimerService(PromiseService)
    {
        var isPlaying = false,
            maxEventTime = 0;

        return {
            start: function()
            {
                // only "start" once this promise has resolved.
                PromiseService.get_promise()
                    .then(function(value)
                    {
                        maxEventTime = PromiseService.maxEventTime();
                        isPlaying = true;
                    });
            },
            get_isPlaying: function() {
                return isPlaying;
            }
        };
    });

我想测试一下,maxEventTime只在Promise成功后设置。

我的测试:

describe('Service: BasicTimerService', function () {
    beforeEach(module('myApp'));

    var mockPromiseService,
        BasicTimerService,
        $q,
        $rootScope,
        deferred;

    beforeEach(function(){
        mockPromiseService = {
            maxEventTime: function() {
                return 17000;
            }
        };

        module(function($provide){
            $provide.value('PromiseService', mockPromiseService);
        });

        inject(function(_BasicTimerService_, _$q_, _$rootScope_){
            BasicTimerService = _BasicTimerService_;
            $rootScope = _$rootScope_;
            $q = _$q_;
        });

        mockPromiseService.get_promise = function() {
            var deferred = $q.defer();
            deferred.resolve('hello world');
            return deferred.promise;
        };
    });

    it('should get maxEventTime once the promise is resolved.', function() {
        spyOn(mockPromiseService, 'get_promise').and.callThrough();
        spyOn(mockPromiseService, 'maxEventTime').and.callThrough();

        $rootScope.$apply();

        BasicTimerService.start();

        expect(mockPromiseService.get_promise).toHaveBeenCalled();
        expect(BasicTimerService.get_isPlaying()).toBe(true);
        expect(mockPromiseService.maxEventTime).toHaveBeenCalled();
    });
});

确定mockPromiseService.get_promise()调用成功(第一个expect()成功),但是BasicTimerService中的.then()子句好像没有调用,想不通为什么。

我试过 $rootScope.$apply();$rootScope.$digest(); 都没有用。

关于为什么这不起作用的任何想法?

您的 start() 方法是异步的,因此如果它正常工作,当您检查它的值时 get_isPlaying() 将不会是 true。您需要等待结果(将 start() 修改为 return Benjamin Gruenbaum 建议的承诺:

it('should get maxEventTime once the promise is resolved.', function(done) {
    spyOn(mockPromiseService, 'get_promise').and.callThrough();
    spyOn(mockPromiseService, 'maxEventTime').and.callThrough();

    $rootScope.$apply();

    BasicTimerService.start().then(function () {
        expect(mockPromiseService.get_promise).toHaveBeenCalled();
        expect(BasicTimerService.get_isPlaying()).toBe(true);
        expect(mockPromiseService.maxEventTime).toHaveBeenCalled();
        done();
    });
});

您也不需要在 get_promise 中使用 $q.defer()(您几乎不需要使用 $q.defer()):

mockPromiseService.get_promise = function() {
    return $q.when('hello world');
};

注意:根据 $q 文档,您似乎可以使用 $rootScope.$apply() 来获取承诺值以传播到它们的 then 处理程序,但我认为您应该将承诺视为承诺并使用 .then() 等来测试它们。