簡體   English   中英

在JasmineJS測試中未觸發AngularJS Promise回調

[英]AngularJS Promise Callback Not Trigged in JasmineJS Test

問題介紹

我正在嘗試對包裝Facebook JavaScript SDK FB對象的AngularJS服務進行單元測試; 但是,測試不起作用,我無法弄清楚原因。 此外,當我在瀏覽器中運行它而不是使用Karma測試運行器運行的JasmineJS單元測試時,服務代碼可以正常工作。

我正在通過$q對象使用Angular promises測試異步方法。 我將測試設置為使用Jasmine 1.3.1異步測試方法異步運行,但waitsFor()函數永遠不會返回true (請參閱下面的測試代碼),它只是在5秒后超時 (Karma尚未附帶Jasmine 2.0異步測試API)。

我想這可能是因為promise的then()方法永遠不會被觸發(我設置了一個console.log()來表示),即使我正在調用$scope.$apply()當異步方法返回,讓Angular知道它應該運行一個摘要周期並觸發then()回調...但我可能是錯的。

這是運行測試時出現的錯誤輸出:

Chrome 32.0.1700 (Mac OS X 10.9.1) service Facebook should return false
  if user is not logged into Facebook FAILED
  timeout: timed out after 5000 msec waiting for something to happen
Chrome 32.0.1700 (Mac OS X 10.9.1):
  Executed 6 of 6 (1 FAILED) (5.722 secs / 5.574 secs)

代碼

這是我對服務的單元測試( 請參閱內聯注釋,解釋到目前為止我發現的內容 ):

'use strict';

describe('service', function () {
  beforeEach(module('app.services'));

  describe('Facebook', function () {
    it('should return false if user is not logged into Facebook', function () {
      // Provide a fake version of the Facebook JavaScript SDK `FB` object:
      module(function ($provide) {
        $provide.value('fbsdk', {
          getLoginStatus: function (callback) { return callback({}); },
          init: function () {}
        });
      });

      var done = false;
      var userLoggedIn = false;

      runs(function () {
        inject(function (Facebook, $rootScope) {
          Facebook.getUserLoginStatus($rootScope)
            // This `then()` callback never runs, even after I call
            // `$scope.$apply()` in the service :(
            .then(function (data) {
              console.log("Found data!");
              userLoggedIn = data;
            })
            .finally(function () {
              console.log("Setting `done`...");
              done = true;
            });
        });
      });

      // This just times-out after 5 seconds because `done` is never
      // updated to `true` in the `then()` method above :(
      waitsFor(function () {
        return done;
      });

      runs(function () {
        expect(userLoggedIn).toEqual(false);
      });

    }); // it()
  }); // Facebook spec
}); // Service module spec

這是我正在測試的Angular服務( 請參閱內聯注釋,解釋我到目前為止所發現的內容 ):

'use strict';

angular.module('app.services', [])
  .value('fbsdk', window.FB)
  .factory('Facebook', ['fbsdk', '$q', function (FB, $q) {

    FB.init({
      appId: 'xxxxxxxxxxxxxxx',
      cookie: false,
      status: false,
      xfbml: false
    });

    function getUserLoginStatus ($scope) {
      var deferred = $q.defer();
      // This is where the deferred promise is resolved. Notice that I call
      // `$scope.$apply()` at the end to let Angular know to trigger the
      // `then()` callback in the caller of `getUserLoginStatus()`.
      FB.getLoginStatus(function (response) {
        if (response.authResponse) {
          deferred.resolve(true);
        } else {
          deferred.resolve(false)
        }
        $scope.$apply(); // <-- Tell Angular to trigger `then()`.
      });

      return deferred.promise;
    }

    return {
      getUserLoginStatus: getUserLoginStatus
    };
  }]);

資源

以下列出了我已經嘗試解決此問題的其他資源。

摘要

請注意,我知道Facebook JavaScript SDK已經有其他Angular庫 ,例如:

我現在對使用它們不感興趣,因為我想學習如何自己編寫Angular服務。 所以請保持答案僅限於幫助我解決代碼中的問題,而不是建議我使用別人的問題

那么,話雖如此, 有誰知道為什么我的測試不起作用?

TL; DR

從您的測試代碼中調用$rootScope.$digest() ,它將通過:

it('should return false if user is not logged into Facebook', function () {
  ...

  var userLoggedIn;

  inject(function (Facebook, $rootScope) {
    Facebook.getUserLoginStatus($rootScope).then(function (data) {
      console.log("Found data!");
      userLoggedIn = data;
    });

    $rootScope.$digest(); // <-- This will resolve the promise created above
    expect(userLoggedIn).toEqual(false);
  });
});

這里的人物。

注意:我刪除了run()wait()調用,因為這里不需要它們(沒有執行實際的異步調用)。

很長的解釋

這是正在發生的事情:當你調用getUserLoginStatus() ,它會在內部運行FB.getLoginStatus() ,它會立即執行它的回調,因為你已經嘲笑它正是這樣做的。 但是$scope.$apply()調用是在該回調中,因此它在測試中的.then()語句之前執行。 從那時then()創建了一個新的承諾,需要一個新的摘要來解決該承諾。

我相信這個問題不會在瀏覽器中發生,因為有兩個原因:

  1. FB.getLoginStatus()不會立即調用它的回調,所以任何then()調用都會先運行; 要么
  2. 應用程序中的其他內容會觸發新的摘要周期。

因此,要將其包裝起來,如果您在測試中明確或不明確地創建了承諾,則必須在某個時刻觸發摘要周期,以便該承諾得到解決。

    'use strict';

describe('service: Facebook', function () {
    var rootScope, fb;
    beforeEach(module('app.services'));
    // Inject $rootScope here...
    beforeEach(inject(function($rootScope, Facebook){
        rootScope = $rootScope;
        fb = Facebook;
    }));

    // And run your apply here
    afterEach(function(){
        rootScope.$apply();
    });

    it('should return false if user is not logged into Facebook', function () {
        // Provide a fake version of the Facebook JavaScript SDK `FB` object:
        module(function ($provide) {
            $provide.value('fbsdk', {
                getLoginStatus: function (callback) { return callback({}); },
                init: function () {}
            });
        });
        fb.getUserLoginStatus($rootScope).then(function (data) {
            console.log("Found data!");
            expect(data).toBeFalsy(); // user is not logged in
        });
    });
}); // Service module spec

這應該做你想要的。 通過使用beforeEach設置rootScope和afterEach來運行apply,您還可以輕松擴展測試,以便在用戶登錄時添加測試。

從我可以看到你的代碼無法工作的問題是你沒有注入$ scope。 Michiels的回答是有效的,因為他注入$ rootScope並調用摘要周期。 但是你的$ apply()是一個更高級別的調用摘要周期所以它也可以工作......但是! 只有將它注入服務本身。

但我認為服務不會創建一個$ scope子項,所以你需要注入$ rootScope本身 - 據我所知,只有控制器允許你注入$ scope作為他們的工作來創建一個$ scope。 但這是一個猜測,我不是百分百肯定的。 我會嘗試使用$ rootScope,因為你知道應用程序有一個來自ng-app創建的$ rootScope。

'use strict';

angular.module('app.services', [])
  .value('fbsdk', window.FB)
  .factory('Facebook', ['fbsdk', '$q', '$rootScope' function (FB, $q, $rootScope) { //<---No $rootScope injection

    //If you want to use a child scope instead then --> var $scope = $rootScope.$new();
    // Otherwise just use $rootScope

    FB.init({
      appId: 'xxxxxxxxxxxxxxx',
      cookie: false,
      status: false,
      xfbml: false
    });

    function getUserLoginStatus ($scope) { //<--- Use of scope here, but use $rootScope instead
      var deferred = $q.defer();
      // This is where the deferred promise is resolved. Notice that I call
      // `$scope.$apply()` at the end to let Angular know to trigger the
      // `then()` callback in the caller of `getUserLoginStatus()`.
      FB.getLoginStatus(function (response) {
        if (response.authResponse) {
          deferred.resolve(true);
        } else {
          deferred.resolve(false)
        }
        $scope.$apply(); // <-- Tell Angular to trigger `then()`. USE $rootScope instead!
      });

      return deferred.promise;
    }

    return {
      getUserLoginStatus: getUserLoginStatus
    };
  }]);

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM