AngularJS unit test with $http request never fires

2019-08-15 08:10发布

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.

2条回答
2楼-- · 2019-08-15 08:33

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...
}));
查看更多
走好不送
3楼-- · 2019-08-15 08:46

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.

查看更多
登录 后发表回答