【问题标题】:AngularJS : testing a factory that returns a promise, while mocking a service that uses $httpAngularJS:测试一个返回承诺的工厂,同时模拟一个使用 $http 的服务
【发布时间】:2015-09-21 16:27:55
【问题描述】:

我有一个具有以下方法的服务(以及其他方法),它返回一个 $http 承诺

  function sessionService($http, serviceRoot) {
    return {
        getAvailableDates: function () {
            return $http.get(serviceRoot + '/session/available_dates');
        }
    };
  };

  angular.module('app').service('sessionService', ['$http', 'serviceRoot', sessionService]);

然后是另一个工厂,它有点包装它并将数据缓存/添加到 localStorage。这会返回一个常规的承诺

angular.module('app')
    .factory('AvailableDates', AvailableDates);

AvailableDates.$inject = ['sessionService', '$window', '$q'];

function AvailableDates(sessionService, $window, $q) {
    var availableDates = [];

    return {
        getAvailableDates: getAvailableDates
    };

    function getAvailableDates() {
        var deferred = $q.defer();
        var fromStorage = JSON.parse($window.sessionStorage.getItem('validDates'));

        if (availableDates.length > 0) {
            deferred.resolve(availableDates);
        } else if (fromStorage !== null) {
            deferred.resolve(fromStorage);
        } else {
            sessionService.getAvailableDates()
                .success(function (result) {
                    availableDates = result;
                    $window.sessionStorage.setItem('validDates', JSON.stringify(availableDates));
                    deferred.resolve(availableDates);
                });
        }
        return deferred.promise;
    }
}

这一切都很好。我的问题是在模拟 sessionService 时我不知道如何测试这个东西。我已经阅读了所有相关的 stackoverflow 问题,并尝试了各种不同的方法,但均无济于事。

这是我的测试目前的样子:

describe('testing AvailableDates factory', function () {
    var mock, service, rootScope, spy, window, sessionStorageSpy, $q;
    var dates = [ "2014-09-27", "2014-09-20", "2014-09-13", "2014-09-06", "2014-08-30" ];
    var result;

    beforeEach(module('app'));

    beforeEach(function() {
        return angular.mock.inject(function (_sessionService_, _AvailableDates_, _$rootScope_, _$window_, _$q_) {
            mock = _sessionService_;
            service = _AvailableDates_;
            rootScope = _$rootScope_;
            window = _$window_;
            $q = _$q_;
        });
    });

    beforeEach(inject(function () {
        // my service under test calls this service method
        spy = spyOn(mock, 'getAvailableDates').and.callFake(function () {
            return {
                success: function () {
                    return [ "2014-09-27", "2014-09-20", "2014-09-13", "2014-09-06", "2014-08-30" ];
                },
                error: function() {
                    return "error";
                }
            };
        });

        spyOn(window.sessionStorage, "getItem").and.callThrough();
    }));

    beforeEach(function() {
        service.getAvailableDates().then(function(data) {
            result = data;
            // use done() here??
        });
    });

    it('first call to fetch available dates hits sessionService and returns dates from the service', function () {
        rootScope.$apply(); // ??

        console.log(result); // this is printing undefined

        expect(spy).toHaveBeenCalled();  // this passes
        expect(window.sessionStorage.getItem).toHaveBeenCalled(); // this passes
    });
});

我尝试了各种方法,但不知道如何测试 AvailableDates.getAvailableDates() 调用的结果。当我使用 done() 时,我收到错误: 超时 - 在 jasmine.DEFAULT_TIMEOUT_INTERVAL 指定的超时内未调用异步回调(我已尝试覆盖此值,但运气不好)。

如果我取出 done(),并在调用 .then 之后调用 rootScope.$apply(),我会得到一个未定义的值作为结果。

我做错了什么?

【问题讨论】:

  • 不确定,但也可能将 promise 存储在变量中 (thePromise),然后在您的测试中调用 thePromise.done() 而不是 rootScope.$apply()
  • done() (在我的测试中)指的是 jasmine 的 done() jasmine.github.io/2.2/… 我正在使用 Angular 的承诺库 $q docs.angularjs.org/api/ng/service/$q 它有一个 then() 调用而不是 jquery 的 done()打电话
  • 为了进一步混淆,Angular 的 $http 库返回了一种特殊的 promise(它有 .success() 和 .error 回调)。

标签: javascript angularjs unit-testing jasmine angular-promise


【解决方案1】:

我在您的示例中看到了更多问题。

主要问题是模拟中的成功定义。成功是一个函数,它有一个函数作为参数——回调。接收到数据时调用回调 - 数据作为第一个参数传递。

return {
    success: function (callback) {
        callback(dates);
    }
};

这里是简化的工作示例http://plnkr.co/edit/Tj2TZDWPkzjYhsuSM0u3?p=preview

在这个例子中,mock 通过模块函数(来自 ngMock)传递给提供者——你可以通过键(服务名称)和值(实现)传递对象。该实现将用于注入。

module({
      sessionService:sessionServiceMock
});

我认为测试逻辑应该在一个函数(测试)中,将其拆分为 beforeEach 并且测试不是一个好的解决方案。测试是我的例子;它更具可读性并且具有清晰的分离部分 - 排列、动作、断言。

inject(function (AvailableDates) {
    AvailableDates.getAvailableDates().then(function(data) {
      expect(data).toEqual(dates);
      done();
    });

    rootScope.$apply(); // promises are resolved/dispatched only on next $digest cycle

    expect(sessionServiceMock.getAvailableDates).toHaveBeenCalled();
    expect(window.sessionStorage.getItem).toHaveBeenCalled();
  });

【讨论】:

    猜你喜欢
    • 2014-09-27
    • 1970-01-01
    • 2015-10-18
    • 1970-01-01
    • 2014-04-12
    • 1970-01-01
    • 2017-11-12
    • 2014-07-05
    • 1970-01-01
    相关资源
    最近更新 更多