2016-06-06 80 views
0

我的問題:在Karma中,我在測試實際服務時嘲笑注入服務。模擬的服務獲取一些數據,併發回一個承諾。我無法弄清楚我做錯了什麼。承諾在被測服務中使用模擬服務解決

我知道實際的「登錄」機制存在一些問題,但我用它來說明這個Karma問題。我不打算將它用作生產代碼。 (然而,爲了更好地說明這個問題的任何建議,歡迎!)

首先,我在一個通用的.js文件寫到這,並說:「節點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} 

現在,我嘗試在噶重複這個...

TestService的

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

}); 

爲什麼承諾沒有得到解決?我已經試過用$ rootScope。$ digest()基於類似的問題閱讀過,但似乎沒有得到testCb被調用。

+0

你在哪裏調用'$ rootScope。$ digest()'?它不在代碼中。是否有一個真正的原因,爲什麼承諾鏈被回調污染? 'getByUserName'是否由於意圖或錯誤而返回本地許諾而不是'$ q'? – estus

+0

$ rootScope。$ digest() - 我把它放在各個地方的代碼中,但它對結果從來沒有任何影響。所以,我現在沒有在那裏。坦率地說,我不知道如何使用它。 getByUsername返回本地承諾的意圖。坦率地說,我真的不知道如何使用$ q。看完這篇文章後,http://www.codelord。net/2015/09/24/$ q-dot-defer-youre-doing-it-wrong /,我只是舉起雙手,想着又一天節省$ q。 – westandy

+0

我已經在JS工作了兩年,Angular已經工作了6個月。我覺得我已經有了基礎知識,但是像$ digest和$ q這樣的東西已經教會了我,否則。因果報應和單位測試指令幾乎壓垮了我的靈魂(參見我的其他關於業力指令的問題)。 – westandy

回答

0

有人讓我知道,如果我能給'estus'這個答案的功勞。

神奇的是在$超時角模擬服務: https://docs.angularjs.org/api/ngMock/service/ $超時

和$ Q服務: https://docs.angularjs.org/api/ng/service/ $ Q

我更新了兩件事情。首先,我的MockUserService使用$ q.defer而不是本地承諾。正如'estus'所言,$ q承諾是同步的,這在業力 - 茉莉花測試中很重要。

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

我所做的下一次更新與$超時服務:

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 $和超時在我的測試,我不得不更新我的注射方法我beforeEach:

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

['$ q.defer'](https://github.com/petkaantonov/bluebird/wiki/Promise-anti-patterns#the-deferred-anti-pattern)被稱爲Deferred antipattern。這是爲了兼容jQuery,可以使用'$ q.resolve(...)'(Angular 1.4+)或'$ q.when(...)'代替。 '$ timeout.verifyNoPendingTasks()'只有當'$ timeout.flush'與'max delay'參數一起使用時才需要,以確保隊列中沒有更大的超時。 – estus

+1

有關'$ timeout'測試的更多信息,請參閱http://stackoverflow.com/a/37527210/3731501。 – estus