vendredi 3 juillet 2015

Angularjs - testing a factory that returns a promise, while mocking a service that uses $http

I've got a service that has the following method (among others), which returns an $http promise

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

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

And then another factory that kinda wraps it and caches/adds data to localStorage. This returns a regular promise

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

This all works fine. My problem is I can't figure out how to test this thing while mocking the sessionService. I've read all the related stackoverflow questions, and tried all kinds of different things, to no avail.

Here's what my test currently looks like:

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

I've tried various things but can't figure out how to test the result of the AvailableDates.getAvailableDates() call. When I use done(), I get the error: Timeout - Async callback was not invoked withing timeout specified by jasmine.DEFAULT_TIMEOUT_INTERVAL (I've tried overriding this value, no luck).

If I take out the done(), and just call rootScope.$apply() after the .then is called, I get an undefined value as my result.

What am I doing wrong?

Aucun commentaire:

Enregistrer un commentaire