robert.bo.roth robert.bo.roth - 3 months ago 12
AngularJS Question

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

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.

Answer

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