AngularJS unit test with $http request never fires .then() callbacks

北战南征 提交于 2019-12-11 06:59:29

问题


I'm trying to get unit tests running with mocked responses in separate json files. The tests were working when I used $q to return promises resolved manually in my OpsService, but when I tried to make them into actual $http requests to return the actual json files, they no longer worked.

edit: I've tried $httpBackend.flush(), $rootScope.$apply(), and $rootScope.$digest() but none of those seem to resolve the promises.

My service:

OpsService.service('OpsService', function ($q, $http) {

    this.get = {
        bigTen : function () {
            // var defer = $q.defer();
            // defer.resolve({"data":{"alltime":125077,"record":{"date":"2016-07-19","count":825},"today":281}});
            // return defer.promise;

            return $http({
                method: 'GET',
                url: '/jsonMocks/api/big-ten.json'
            }).then(function (response) {
                console.log('bigTen data');
                console.log(response);
                return response;
            }, function (error) {
                console.log('ERROR');
                console.log(error);
            });
        },

        dashboardData : function () {
            console.log('blahhhhh');
            return $http({
                method: 'GET',
                url: '/jsonMocks/api/dashboard-data.json'
            }).then(function (response) {
                console.log('dasbhoard data');
                console.log(response);
                return response;
            }, function (error) {
                console.log('ERROR');
                console.log(error);
            });
        }
    };

    return this;
});

My controller:

homeModule.controller('HomeController', function ($scope, OpsService) {
    var ctrl = this;
    ctrl.loading = {
        topMetrics: true,
        dashboardData: true
    };

    function init() {
        ctrl.topMetricData();

        ctrl.getDashboardData();

        ctrl.initialized = true;
    }

    ctrl.topMetricData = function () {
        ctrl.loading.topMetrics = true;
        console.log('in topMetricData()');
        return OpsService.get.bigTen().then(function (bigTen) {
            console.log('bigTenControllerCallback');

            ctrl.loading.topMetrics = false;
            return bigTen;
        });
    };

    ctrl.getDashboardData = function () {
        ctrl.loading.dashboardData = true;
        console.log('in getDashboardData()');
        return OpsService.get.dashboardData().then(function (response) {
            console.log('getDashboardDataController Callback');

            ctrl.loading.dashboardData = false;
            return dashboardData;
        });
    };

    init();
});

My test:

describe('home section', function () {
    beforeEach(module('ngMockE2E'));
    beforeEach(module('templates-app'));
    beforeEach(module('templates-common'));
    beforeEach(module('LROps.home'));

    var $rootScope, $scope, $httpBackend, createController, requestHandler;

    beforeEach(inject(function($injector, _$rootScope_, _$controller_, _OpsService_) {
        $rootScope = _$rootScope_;

        $httpBackend = $injector.get('$httpBackend');

        var bigTenJson = readJSON('jsonMocks/api/big-ten.json');
        console.log(bigTenJson);
        $httpBackend.when('GET', '/jsonMocks/api/big-ten.json')
            .respond(200, { data: bigTenJson });
        // .respond(200, { data: 'test1' });

        var dashboardDataJson = readJSON('jsonMocks/api/dashboard-data.json');
        console.log(dashboardDataJson);
        $httpBackend.when('GET', '/jsonMocks/api/dashboard-data.json')
            .respond(200, { data: dashboardDataJson });
        // .respond(200, { data: 'test2' });

        var $controller = _$controller_;
        createController = function() {
            $scope = $rootScope.$new();
            return $controller('HomeController', {
                $scope : $scope,
                OpsService : _OpsService_
            });
        };
    }));

    afterEach(function() {
        $httpBackend.verifyNoOutstandingExpectation();
        $httpBackend.verifyNoOutstandingRequest();
    });

    it('should retrieve big ten data', inject(function () {
        $httpBackend.expect('GET', '/jsonMocks/api/big-ten.json');
        $httpBackend.expect('GET', '/jsonMocks/api/dashboard-data.json');

        // Controller Setup
        var ctrl = createController();

        // Initialize
        $rootScope.$apply();
        $rootScope.$digest();

        expect(ctrl.topMetrics.display.messages.count).toEqual(745);
    }));

});

So, none of my console.log() are firing in the .then() callbacks. If I change back to returning a $q.defer().resolve(response).promise object, it seems to work fine.

Note: I'm using karma-read-json to read the JSON files and respond accordingly in my tests. As far as I can tell, they're being read properly, it's just the promises aren't being resolved so the .then() callbacks can execute.


回答1:


The first thing is that each asserted request should be mocked request. The requests should be flushed with $httpBackend.flush(), it triggers a digest, $rootScope.$apply() and $rootScope.$digest() (they duplicate each other) shouldn't be called.

The second thing is that it shouldn't be done in controller spec! Controller is a separate unit that depends on a service, it should be tested in isolation with mocked service. OpsService is a different unit.

it('should retrieve big ten data', inject(function () {
    $httpBackend.expect('GET', '/jsonMocks/api/big-ten.json').respond(200, ...);
    $httpBackend.expect('GET', '/jsonMocks/api/dashboard-data.json').respond(200, ...);

    OpsService.get.bigTen().then(function (result) {
       expect(result)...
    }, function (err) {
       throw err;
    });
    OpsService.get.dashboardData()...

    $httpBackend.flush();
}));

it('should test a controller', inject(function () {
    var OpsServiceMock = { get: {
       bigTen: jasmine.createSpy().and.returnValue(...),
       dashboardData: jasmine.createSpy().and.returnValue(...)
    } };

    $scope = $rootScope.$new();

    var ctrl = $controller('HomeController', {
        $scope : $scope,
        OpsService : OpsServiceMock 
    });

    $rootScope.$digest();

    expect(OpsServiceMock.get.bigTen).toHaveBeenCalled();
    expect(OpsServiceMock.get.dashboardData).toHaveBeenCalled();
    expect...
}));



回答2:


EDIT: Looking at the documentation for $httpBackend, the expect and when methods don't work together. They're different options for setting up the backend.

expect looks like it adds an expectation that the call will happen, and gives you a .respond() you call call on the result to give what to respond with.

when just lets you set up a response for a particular response without actually saying you expect it.

So in your tests the expect calls are overwriting the when definition you did, and don't return any response because you didn't configure one.

So, I think you can just get rid of the expects and put a flush after your controller like so:

it('should retrieve big ten data', inject(function () {
    // Controller Setup
    var ctrl = createController();

    $httpBackend.flush();

    // Initialize
    $rootScope.$apply();
    $rootScope.$digest();

    expect(ctrl.topMetrics.display.messages.count).toEqual(745);
}));

Or change your whens in the beforeEach to expects and then you probably won't need the flush.



来源:https://stackoverflow.com/questions/39021000/angularjs-unit-test-with-http-request-never-fires-then-callbacks

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!