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

时间:2015-03-02 19:48:44

标签: angularjs unit-testing mocking jasmine promise

我有以下服务(简化为简化):

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;
            }
        };
    });

我希望测试只有在Promise成功后才设置maxEventTime。

我的测试:

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();都无济于事。

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

1 个答案:

答案 0 :(得分:2)

您的start()方法是异步的,因此如果它正常工作,那么当您检查其值时,get_isPlaying()将不会是true。您需要等待结果(修改start()以返回承诺,因为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();
    });
});

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

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

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