【问题标题】:promise resolution with a mocked service within the tested service在测试服务中使用模拟服务承诺解决方案
【发布时间】:2016-10-06 11:08:28
【问题描述】:

我的问题:在 Karma 中,我在测试实际服务时模拟了注入服务。模拟服务获取一些数据,并返回一个承诺。我无法弄清楚我做错了什么。

我知道实际的“登录”机制存在许多问题,但我用它来说明这个 Karma 问题。我不打算将其用作生产代码。 (但是,欢迎提出更好地说明问题的任何建议!)

首先,我把它写在一个通用的 .js 文件中,并说“node testcode.js”

testcode.js

function Login(name,password,callback){
    setTimeout(function(){
        var response;
        var promise = getByUserName();
        promise.then(successCb);

        function successCb(userObj){
            if ( userObj != null && userObj.password === password ) {
                response = { success : true };
            } else {
                response = { success: false, message: 'Username or password is incorrect' };
            }
            callback(response);
        };
    },200);
}

function getByUserName(){
    return Promise.resolve(user);
}

var user = {
    username : 'test',
    id : 'testId',
    password : 'test'
};

var test = undefined;
Login(user.username,user.password,testCb);

function testCb(response){
    test = response;
    console.log("Final: " + JSON.stringify(test));
}

这给了我预期的结果:

Final: {"success":true}

现在,我尝试在 Karma 中重复这一点......

测试服务

(function(){
    "use strict";

    angular.module('TestModule').service('TestService',TestService);
    TestService.$inject = ['$http','$cookieStore','$rootScope','$timeout','RealService'];
})();

    function TestService($http,$cookieStore,$rootScope,$timeout,RealService){
        var service = {};
        service.Login = Login;
        /* more stuff */
        return service;

        function Login(username,password,callback){
            $timeout(function () {
                var response;
                var promise = UserService.GetByUsername(username);
                promise.then(successCb);

                function successCb(user){
                    if (user !== null && user.password === password) {
                        response = { success: true };
                    } else {
                        response = { success: false, message: 'Username or password is incorrect' };
                    }
                    callback(response);
                };
            }, 1000);
        }
    }

我的因果报应测试

describe('TestModule',function(){
    beforeEach(module('TestModule'));

    describe('TestService',function(){

        var service,$rootScope,
        user = {
            username : 'test',
            id : 'testId',
            password : 'test'
        };

        function MockService() {
            return {
                GetByUsername : function() {
                    return Promise.resolve(user);
                }
            };
        };

        beforeEach(function(){
            module(function($provide){
                $provide.service('RealService',MockService);
            });

            inject(['$rootScope','TestService',
                function($rs,ts){
                    $rootScope = $rs;
                    service = ts;
                }]);
        });

        /**
         * ERROR: To the best of my knowledge, this should not pass
         */
        it('should Login',function(){
            expect(service).toBeDefined();

            var answer = {"success":true};
            service.Login(user.username,user.password,testCb);

            //$rootScope.$apply(); <-- DID NOTHING -->
            //$rootScope.$digest(); <-- DID NOTHING -->

            function testCb(response){
                console.log("I'm never called");
                expect(response.success).toBe(false);
                expect(true).toBe(false);
            };
        });
    });

});

为什么承诺没有得到解决?我已经尝试根据我在 SO 上阅读的类似问题来搞乱 $rootScope.$digest() ,但似乎没有任何东西可以调用 testCb

【问题讨论】:

  • 你在哪里打电话给$rootScope.$digest()?它不在代码中。承诺链被回调污染的真正原因是什么? getByUserName 是有意还是无意返回原生承诺而不是 $q
  • $rootScope.$digest() - 我把它放在代码的各个地方,但它从未对结果产生任何影响。所以,我目前没有它在那里。坦率地说,我不知道如何使用它。 getByUsername 有意返回本机承诺。坦率地说,我真的不明白如何使用 $q。读完这篇 codelord.net/2015/09/24/$q-dot-defer-youre-doing-it-wrong 的文章后,我只是举起双手,想着再节省 $q 一天。
  • 我已经从事 JS 工作几年了,Angular 工作了 6 个月。我觉得我已经掌握了基础知识,但是像 $digest 和 $q 这样的东西教会了我其他方面。业力和单元测试指令也几乎摧毁了我的灵魂(请参阅我关于业力指令的其他问题)。
  • $q 承诺是同步的。本机承诺是异步的。 $timeout 是同步的(它应该做$timeout.flush(),在这种情况下没有这样做)。 setTimeout 是异步的(可以用jasmine.clock 处理,现在不是)。应该在 Jasmine 中相应地处理异步规范(它们现在不是)。如果可以在 Angular 应用程序中专门使用 $q 和 $timeout,则应该这样做。我猜你这里有太多未知的概念,应该一一理清。
  • @estus - 谢谢。听起来我需要打破这个烂摊子。我正在测试的模块做我想要的(它是我的应用程序的模拟登录,直到我的服务器端获得授权)。这就是为什么我没有清除您说明的 $timeout 和 $q 问题。

标签: javascript angularjs unit-testing karma-jasmine


【解决方案1】:

如果我可以将这个答案归功于“estus”,请告诉我。

神奇之处在于 $timeout 角度模拟服务: https://docs.angularjs.org/api/ngMock/service/$timeout

和 $q 服务: https://docs.angularjs.org/api/ng/service/$q

我更新了两件事。首先,我的 MockUserService 使用 $q.defer 而不是原生承诺。正如“estus”所说,$q 承诺是同步的,这在 karma-jasmine 测试中很重要。

    function MockUserService(){
        return {
            GetByUsername : function(unusedVariable){
                //The infamous Deferred antipattern:
                //var defer = $q.defer();
                //defer.resolve(user);
                //return defer.promise;
                return $q.resolve(user);
            }
        };
    };

我做的下一个更新是使用 $timeout 服务:

    it('should Login',function(){
        expect(service).toBeDefined();

        service.Login(user.username,user.password,testCb);
        $timeout.flush(); <-- NEW, forces the $timeout in TestService to execute -->
        $timeout.verifyNoPendingTasks(); <-- NEW -->

        function testCb(response) {
            expect(response.success).toBe(true);
        };
    });

最后,因为我在测试中使用了 $q 和 $timeout,我不得不在我的 beforeEach 中更新我的注入方法:

    inject([
        '$q','$rootScope','$timeout','TestService',
        function(_$q_,$rs,$to,ts) {
            $q = _$q_;
            $rootScope = $rs;
            $timeout = $to;
            service = ts;
        }
    ]);

【讨论】:

  • $q.defer 被称为延迟反模式。它用于 jQuery 兼容性,$q.resolve(...) (Angular 1.4+) 或 $q.when(...) 可以代替使用。仅当 $timeout.flush 与 'max delay' 参数一起使用时,$timeout.verifyNoPendingTasks() 才是必需的,以确保队列中没有更大的超时。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-05-14
  • 2019-09-01
  • 2017-12-21
  • 1970-01-01
  • 2015-05-29
  • 1970-01-01
相关资源
最近更新 更多