Reputation: 8663
I am trying to test the response from a service http call that is performed within a controller:
Controller:
define(['module'], function (module) {
'use strict';
var MyController = function ($scope, MyService) {
var vm = this;
$scope.testScope = 'karma is working!';
MyService.getData().then(function (data) {
$scope.result = data.hour
vm.banner = {
'greeting': data.greeting
}
});
};
module.exports = ['$scope', 'MyService', MyController ];
});
Unit test:
define(['require', 'angular-mocks'], function (require) {
'use strict';
var angular = require('angular');
describe("<- MyController Spec ->", function () {
var controller, scope, myService, serviceResponse;
serviceResponse= {
greeting: 'hello',
hour: '12'
};
beforeEach(angular.mock.module('myApp'));
beforeEach(inject(function (_$controller_, _$rootScope_, _MyService_, $q) {
scope = _$rootScope_.$new();
var deferred = $q.defer();
deferred.resolve(serviceResponse);
myService = _MyService_;
spyOn(myService, 'getData').and.returnValue(deferred.promise);
controller = _$controller_('MyController', {$scope: scope});
scope.$apply();
}));
it('should verify that the controller exists ', function() {
expect(controller).toBeDefined();
});
it('should have testScope scope equaling *karma is working*', function() {
expect(scope.testScope ).toEqual('karma is working!');
});
});
});
How can i test the http
request is performed and returns serviceResponse
which binds to $scope.result
and vm.banner greeting
Ive tried:
define(['require', 'angular-mocks'], function (require) {
'use strict';
var angular = require('angular');
describe("<- MyController Spec ->", function () {
var controller, scope, myService, serviceResponse, $httpBackend;
serviceResponse= {
greeting: 'hello',
hour: '12'
};
beforeEach(angular.mock.module('myApp'));
beforeEach(inject(function (_$controller_, _$rootScope_, _MyService_, _$httpBackend_) {
scope = _$rootScope_.$new();
$httpBackend = _$httpBackend_
$httpBackend.expectGET("/my/endpoint/here").respond(serviceResponse);
myService = _MyService_;
spyOn(myService, 'getData').and.callThrough();
controller = _$controller_('MyController', {$scope: scope});
scope.$apply();
}));
it('should call my service and populate scope.result ', function() {
myService.getData();
expect(scope.result ).toEqual(serviceResponse.hour);
});
it('should verify that the controller exists ', function() {
expect(controller).toBeDefined();
});
it('should have testScope scope equaling *karma is working*', function() {
expect(scope.testScope ).toEqual('karma is working!');
});
});
});
With the error:
[should call my service and populate scope.result ----- Expected undefined to be defined.
Upvotes: 3
Views: 3356
Reputation: 5632
I think the problem is that your service returns a promise, so when you do this it
it('should call my service and populate scope.result ', function() {
myService.getData();
expect(scope.result ).toEqual(serviceResponse.hour);
});
your service may not have been resolved yet before it reach the expect
, so you'll have to wait for the then
of your promise first and do the expect inside.
what you can do is assign to promise to $scope.result
var MyController = function ($scope, MyService) {
var vm = this;
$scope.testScope = 'karma is working!';
$scope.result = MyService.getData().then(function (data) {
$scope.result = data.hour
vm.banner = {
'greeting': data.greeting
}
});
};
then in your test, you can do something like
it('should call my service and populate scope.result ', function() {
//myService.getData(); <- you don't have to call this
scope.result.then(function(){
expect(scope.result).toEqual(serviceResponse.hour);
});
});
You will need to mock $httpBackend
and expect for certain request and provide mock response data. here's a snippet from angular docs
beforeEach(inject(function($injector) {
// Set up the mock http service responses
$httpBackend = $injector.get('$httpBackend');
// backend definition common for all tests
$httpBackend.when('GET', '/auth.py')
.respond({userId: 'userX'}, {'A-Token': 'xxx'});
}));
now, whenever $http will call a get to /auth.py
it will respond with the mocked data {userId: 'userX'}, {'A-Token': 'xxx'}
Upvotes: 3
Reputation: 136174
What I understand is while expecting for result, you have made an Ajax & that sets data value when promise get resolved.
Basically when you call myService.getData()
method on controller instantiate, it do an $http
, which will indirectly do $httpBackend
ajax and will return mockData
from it. But as soon as you make a call you are not waiting for call that call to get completed & then you are calling your assert
statement's which fails your statte.
Look at below code comments for explanation
it('should call my service and populate scope.result ', function() {
//on controller instantiation only we make $http call
//which will make your current execution state of your code in async state
//and javascript transfer its execution control over next line which are assert statement
//then obiviously you will have undefined value.
//you don't need to call below line here, as below method already called when controller instantiated
//myService.getData();
//below line gets called without waiting for dependent ajax to complete.
expect(scope.result ).toEqual(serviceResponse.hour);
});
So now you understand somehow you need to tell our test code that, we need to wait to execute assert statement when ajax call gets over. So for that you could use $httpBackend.flush()
method what that will help you in that case. It will clear out all $httpBackend
calls queue clear, before passing control to next line.
it('should call my service and populate scope.result ', function() {
$httpBackend.flush();// will make sure above ajax code has received response
expect(scope.result ).toEqual(serviceResponse.hour);
});
Upvotes: 2
Reputation: 7798
You shouldn't be testing the behavior of your service in your controller. Test the controller and the service in separate tests.
Controller test
In your controller you shouldn't care where the data from the service came from, you should test that the returned result is being used as expected. Verify that your controller calls the desired method when it's inited and that the values of $scope.result
and vm.banner
are what you expect.
Something like:
it('should have called my service and populated scope.result ', function() {
expect(myService.getData).toHaveBeenCalled();
expect(scope.result).toEqual(serviceResponse.hour);
expect(vm.banner).toEqual({greeting:serviceResponse.greeting});
});
Service test
Your service test in the other hand should be aware of the $http
call and should be validating the response, so using the same resources from @Luie Almeda response, write a separate test for your service, calling the method getData()
that performs the mocked $http call and returns the desired result.
Something like:
it('Should return serviceResponse', function () {
var serviceResponse= {
greeting: 'hello',
hour: '12'
};
var myData,
$httpBackend.whenGET('GET_DATA_URL').respond(200, userviceResponse);
MyService.getData().then(function (response){
myData = response
})
$httpBackend.flush();
expect(myData).toBe(serviceResponse);
});
You need to replace GET_DATA_URL
with the correct url being called by MyService.getData()
. Always try to keep your tests in the same modules as your code. Test your controller code in your controller tests and your service code in your service tests.
Upvotes: 2