2016-03-04 72 views
2

我有了這樣的茉莉花單元測試不是在等待承諾的分辨率

(function() { 
    angular 
     .module('app') 
     .factory('myService', ['$q', 'asyncService', 

    function($q, asyncService) { 

     var myData = null; 

     return { 
      initialize: initialize, 
     }; 

     function initialize(loanId){ 
      return asyncService.getData(id) 
       .then(function(data){ 
        console.log("got the data!"); 
        myData = data; 
      }); 
     } 
    }]); 
})(); 

我想單元測試initialize功能,我想在茉莉這樣一個異步依賴的角度服務:

describe("Rate Structure Lookup Service", function() { 

    var $q; 
    var $rootScope; 
    var getDataDeferred; 
    var mockAsyncService; 
    var service; 

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

     module(function ($provide) { 
      $provide.value('asyncService', mockAsyncService); 
     }); 

     inject(function(_$q_, _$rootScope_, myService) { 
      $q = _$q_; 
      $rootScope = _$rootScope_; 
      service = myService; 
     }); 

     getDataDeferred = $q.defer(); 

     mockAsyncService = { 
      getData: jasmine.createSpy('getData').and.returnValue(getDataDeferred.promise) 
     }; 
    }); 

    describe("Lookup Data", function(){ 
     var data; 

     beforeEach(function(){ 
      testData = [{ 
       recordId: 2, 
       effectiveDate: moment("1/1/2015", "l") 
      },{ 
       recordId: 1, 
       effectiveDate: moment("1/1/2014", "l") 
      }]; 
     }); 

     it("should get data", function(){ 
      getDataDeferred.resolve(testData); 

      service.initialize(1234).then(function(){ 
       console.log("I've been resolved!"); 
       expect(mockAsyncService.getData).toHaveBeenCalledWith(1234); 
      }); 

      $rootScope.$apply(); 
     }); 
    }); 
}); 

沒有控制檯消息出現,並且測試似乎只是在沒有承諾解決的情況下飛行。我雖然$rootScope.$apply()會做,但似乎不是。

UPDATE

@estus是正確的,$rootScope.$appy()足以引發所有承諾的分辨率。似乎這個問題是在我嘲笑的異步服務。我改變了它從

mockAsyncService = { 
    getData: jasmine.createSpy('getData').and.returnValue(getDataDeferred.promise) 
}; 

mockAsyncService = { 
    getData: jasmine.createSpy('getData').and.callFake(
     function(id){ 
      return $q.when(testData); 
    }) 
}; 

,我設置testData什麼,我需要爲測試而不是調用getDataDeferred.resolve(testData)。在此更改之前,正在注入mockAsyncService,但getDataDeferred的承諾從未得到解決。

我不知道這是否是在注射順序beforeEach或什麼。更奇怪的是,這必須是callFake。使用.and.returnValue($q.when(testData))仍然可以解決問題。

+1

是的,我已經刪除了我的答案,這部分是不相關的(它是在beforeEach競爭條件,而不是延遲本身創建擺在首位的問題),但使用'callFake'用'$ q.when'或'$ q.resolve'允許選擇實際的'$ q'實例,並在每次調用時使用新的承諾(如果間諜在同一規範中被多次調用,這很重要)。同樣,這兩種情況都應該按照預期的方式工作,放入'inject'中。 – estus

+0

將'scope。$ apply()'添加到我的'it'方法的第一行。 「$ q.when」與我在做的事情沒有任何功能上的區別,但是讓方法更加整潔。非常感謝你們兩位。 – JonathanPeel

回答

3

角承諾在測試過程中是同步的,$rootScope.$apply()是足以讓他們在規範的最後解決。

除非asyncService.getData返回一個真正的諾言,而不是$q承諾(和它不會在這種情況下),異步不是茉莉花問題。

Jasmine promise matchers庫是用於測試角的承諾格外好。除了明顯缺乏冗長之外,它在這種情況下提供了寶貴的反饋。雖然這

rejectedPromise.then((result) => { 
    expect(result).toBe(true); 
}); 

規範將當它不應該傳球,這

expect(pendingPromise).toBeResolved(); 
expect(rejectedPromise).toBeResolvedWith(true); 

將失敗,有意義的消息。

與測試代碼的實際問題是beforeEach優先。角引導過程不同步。

getDataDeferred = $q.defer()應放入inject區塊,否則將在模塊被引導之前執行並且$q被注入。 同樣的問題mockAsyncService使用getDataDeferred.promise

在最好情況下,代碼會拋出錯誤,因爲在undefined上調用defer方法。而在最壞的情況下(這就是爲什麼像this.$q規格性能要優於局部變量套件的原因)$q屬於從以前的規範的注射器,從而$rootScope.$apply()不會有任何效果在這裏。

4

您需要將可選的完成參數傳遞給您的塊中的回調函數。否則,茉莉花無法知道你正在測試一個異步函數 - 異步函數立即返回。

這裏的重構:

it("should get data", function(done){ 

    service.initialize(1234).then(function(){ 
     console.log("I've been resolved!"); 
     expect(mockAsyncService.getData).toHaveBeenCalledWith(1234); 
     done(); 
    }); 
}); 
+1

@ PaulD'Ambra謝謝。我不確定投票是從哪裏來的......我確定這是基於他的問題的解決方案。他的邏輯可能在某個地方是錯誤的,但茉莉花測試立即完成的原因是因爲完成的參數沒有通過。 –

+0

@TateThurston標題中沒有任何內容,但實際上它是Angular $ q promise,在這裏被使用,它被同步測試,不需要'done'。 – estus

0

這裏有一些(片狀,背的最啤酒墊)的指針。不幸的是,我無法知道它們是否是實際的錯誤,或者因爲您「簡化了」代碼而導致它們是「拼寫錯誤」。

首先,沒有理由不提供asyncService作爲服務,並且內聯。試試這個:

$provide.service('asyncService', function() { 
    // asyncService implementation 
}); 

另外,我不相信這種依賴注入會起作用。

inject(function(_$q_, _$rootScope_, myService) { 
    $q = _$q_; 
    $rootScope = _$rootScope_; 
    service = myService; 
}); 

因爲DI容器不知道myServiceProvider。你可以試試這個:

inject(function(_$q_, _$rootScope_, _asyncService_) { 
    $q = _$q_; 
    $rootScope = _$rootScope_; 
    service = _asyncService_; 
}); 

這會工作,因爲你調用$提供'asyncService'作爲一個參數。

另外,您沒有正確使用$ promise api。你沒有在你的單元測試中返回一個resolve()對.then()的承諾。嘗試使用備用實施asyncService與此類似:

$provide.service('asyncService', function() { 
    this.getData = function() { 
     return $q(function(resolve, reject) { 
     resolve('Promise resolved'); 
     }); 
    } 
}); 

檢查docs for $q

你可以在你這樣的單元測試這個間諜。在beforeEach()函數中沒有理由調用間諜。

jasmine.spyOn(service, 'getData').and.callThrough(); 

您期望的()看起來不錯。

讓我知道如果任何這可以幫助你。