Provider $get method is not called in jasmine unit test - javascript

I am using custom provider in my app.js to make backend call and then in controllers just injecting that provider and getting promise result (I make this because instead to call getLoggedUser in every controller I am just getting results from provider) and instead of making 10 calls for example I will make just one in provider). But I am unable to test this provider $get method and test is failing because backend call is never made.
I am getting error :
Error: Unexpected request: GET https://localhost:8443/user/current
app.js
angular.module('app', [
...
])
.config(config)
.run(run)
.controller('MainCtrl', MainCtrl)
.value('version', '1.1.0')
.provider('userProvider', function(){
this.$get = ['AuthenticationService',
function(AuthenticationService) {
return AuthenticationService.getLoggedUser();
}];
});
TEST
/* jshint undef:false*/
(function() {
'use strict';
describe('ListingController', function() {
var listingController, rootScope, scope, q, mockAuthenticationService, $httpBackend, service;
var provider = {};
var mockCurrentUser = {
id: 1782,
name: "One, Coordinator",
roleId: [
3, 10
],
eauthId: "coodinator1"
};
beforeEach(module('app'));
beforeEach(module('listing'));
beforeEach(function () {
module(function (userProviderProvider) {
provider = userProviderProvider;
});
});
beforeEach(inject(function($rootScope, $controller, $q, _$httpBackend_, _AuthenticationService_, userProvider, $injector) {
rootScope = $rootScope;
scope = rootScope.$new();
$httpBackend = _$httpBackend_;
q = $q;
mockAuthenticationService = _AuthenticationService_;
// provider = userProvider;
service = $injector.invoke(provider.$get);
rootScope.$digest();
listingController = $controller('ListingController', {
$scope : scope,
mockAuthenticationService : _AuthenticationService_
});
}));
describe('getLogged user and init',function(){
it("should call getLogged function", function() {
listingController.getLoggedUser();
rootScope.$digest();
expect(service).toHaveBeenCalled();
});
});
});
})();
Controller
function getLoggedUser() {
userProvider.then(function (data){
// Here I am getting result from back end call from provider which is good
});
If I make something like this in provider:
this.$get = function (AuthenticationService) {
return {
loggedUser : function() {
return AuthenticationService.getLoggedUser();
}
}
}
I can then make something like this in test:
spyOn(provider , 'loggedUser').and.callFake(function() {
var deferred = q.defer();
deferred.resolve(mockCurrentUser);
return deferred.promise;
});
and this will work test will pass, but with this approach in every controlle when I user userProvider.loggedUser().then it will make additional back end call and with above one only once back end call will be made.
Update for Ceylan
If I do like you suggest to call service and in method call getLoggedUser from another service additional calls are being made every time...not just one like I have without function.
.provider('userProvider', function(){
return {
$get: function(AuthenticationService) {
return new userService(AuthenticationService);
}
}
});
service
function userService(AuthenticationService) {
this.getLoggedUser = function() {
return AuthenticationService.getLoggedUser();
}
}

Here is the basic structure:
$httpBackend.when('GET', 'localhost:8443/user/current').respond(200, /*optional response callback function*/);
$httpBackend.expect('GET', 'localhost:8443/user/current');
//here goes the function that makes the actual http request
$httpBackend.flush();
Be sure that you define your httpBackend var - var httpBackend = $httpBackend;.
And also in order to check did the service was called, you must use spy.
spyOn(service, 'method').and.callThrough();
//here goes the function that calls the service
expect(service.method).toHaveBeenCalled();
You combine the two blocks above and you should be able to achieve what you want.
describe('something',function(){
it("should do something", function() {
spyOn(service, 'method').and.callThrough();
$httpBackend.when('GET', 'localhost:8443/user/current').respond(200,/*optional response callback function*/);
$httpBackend.expect('GET', 'localhost:8443/user/current');
//call the function that makes http request and calls your service
rootScope.$digest();
$httpBackend.flush();
expect(service.method).toHaveBeenCalled();
});
});
And about your service:
function myService(){
var svc = this;
svc.myMethod = function(){
return someDataOrPromise;
}
}

Related

Unable to unit test http call happening through the controller

I am trying to write a code to test the service call which is done in my controller . I am trying to unit test that particular function in controller which is doing the service call and bringing the data . Currently i am trying with local json , but it will actually do a service call .
I got to know that first i have to create a spy object but i am getting the error, my goal is to successfully unit test the http call happening in the controller.
i am new to unit testing .Pasting my code , request you to help me please , struggling in this from many days now.Also i have gone through many solutions , they are so different making be confused.Your help is greatly appreciated
Service code :
//xlpApp is the module name
xlpApp.factory('xlpServices', ['$rootScope', '$http', function($rootScope,
$http) {
var xlpServices = {};
xlpServices.getProgramData = function(){
return $http.get(scripts/services/data/unitTesting.json');
};
unitTesting.json code :
{
"name":"unit testing"
}
Controller Code :
$scope.events = {
programData: function(){
xlpServices.getProgramData().then(function(response) {
if (response && response.data) {
$scope.testVariable= response.data.name;
}
});
},
selectSortingType : function(type) {
$scope.selectedSorting = type;
selectedFilter.sortingBy = $scope.selectedSorting;
}
}
$scope.events.programData();
Unit Test Code :
describe('myProgramGpmCtrl', function() {
beforeEach(module('xlp'));
var $controller;
beforeEach(inject(function(_$controller_){
$controller = _$controller_;
}));
describe('service call test', function() {
var xlpServices , myService , $q;
var $scope = {};
beforeEach(inject(function(xlpServices,_$q_){
xlpServices = xlpServices;
$q = _$q_;
var controller = $controller('myProgramGpmCtrl', { $scope: $scope });
myService = jasmine.createSpyObj('xlpServices',['getProgramData']);
}));
it('Service call test ', function() {
expect(myService.getProgramData).toHaveBeenCalled();
});
});
});
ERROR :
Expected spy xlpServices.getProgramData to have been called.
Try something like,
describe('service call test', function() {
var xlpServicesMock , myService , $q;
var $scope = {};
beforeEach(inject(function(xlpServices,_$q_){
xlpServicesMock = xlpServices;
$q = _$q_;
spyOn(xlpServicesMock ,'getProgramData').and.callFake(function() {
// we can return promise instead this custom object
return {
then: (callback, errorCallback) => {
callback('data to be passed to then callback');
/* `callback` function should be invoked when you wanted to test the service for success response with status code 200.
`errorCallback` function should be invoked with 500 status code when you wanted to test the api failure
Ex: callback({status: 200, data: <your data here>);
errorCallback({status: 500, data: <your error data>})
You can prepare the response to be passed as you wanted.
*/
}
};
});
var controller = $controller('myProgramGpmCtrl', { $scope: $scope, xlpServices: xlpServicesMock });
}));
it('Service call test ', function() {
$scope.events.programData();
expect(myService.getProgramData).toHaveBeenCalled();
});
});
There are good resources available online.
check here and here

how to call a function in the controller from service in angular application

I have an Angular app with a controller and a service. I call the service from the controller and the service makes a back end call and should call another function in the controller. I was able to call the service from the controller but was not able to call a function in the controller.
Here is the code. The controller:
(function(){
angular.module("addressModule")
// The controller for the standardized Address
.controller('myController', ['$resource','myService', myControllerFunction]);
myControllerFunction.$inject = ['addressConfig', 'myService', '$resource'];
function myControllerFunction (addressConfig, myService, $resource, $scope) {
var vm = this;
// cannot show what the model is
vm.model = "some model";
myService.callGISFromService(vm.model);
this.callFunctionInController = function(){
console.log("The controller function is called");
}
}
})();
The code for the service is
(function(){
angular.module('addressModule')
.service('myService', ['$resource', myServiceFunction]);
myServiceFunction.$inject = ['$resource'];
function myServiceFunction ($resource) {
this.callGISFromService = function (model) {
var urlForTheGISCall = "some URL";
var resource = $resource(urlForTheGISCall);
console.log("control is in the service");
resource.get().$promise
.then(function successCallback(response) {
this.callFunctionInController();
}), function errorCallback(response) {
console.log("the backend call did not work");
}
};
}
})();
I was able to make the back end call and get the data required but not able to call the function in controller.
The error on the console:
TypeError: this.callFunctionInController is not a function
You might want to take a look at this post, it explains how to use a $broadcast, to establish communication between service - controller.
You could also transform the function in your service into a asynchronously function, so the controller knows when the function has completed successfully or with errors. It would look like this:
(function(){
angular.module('addressModule')
.service('myService', ['$resource', '$q', myServiceFunction]);
myServiceFunction.$inject = ['$resource', '$q'];
function myServiceFunction ($resource) {
this.callGISFromService = function (model) {
var deferred = $q.defer();
var urlForTheGISCall = "some URL";
var resource = $resource(urlForTheGISCall);
console.log("control is in the service");
resource.get().$promise
.then(function successCallback(response) {
deferred.resolve('success');
}), function errorCallback(response) {
deferred.reject('error');
}
return deferred.promise;
};
}
})();
This makes it that you can call your function in the controller like this:
(function(){
angular.module("addressModule")
// The controller for the standardized Address
.controller('myController', ['$resource','myService', myControllerFunction]);
myControllerFunction.$inject = ['addressConfig', 'myService', '$resource'];
function myControllerFunction (addressConfig, myService, $resource, $scope) {
var vm = this;
// cannot show what the model is
vm.model = "some model";
myService.callGISFromService(vm.model).then(function(success){
function(){
console.log("The controller function is called");
}
});
}
})();
I personally would opt for the second option, as it is more elegant and lightweight than using broadcast. Having allot of broadcasts in your app could make it really messy and difficult to read. For more info on asynchronously functions, check out the angularjs documentation
This is the JavaScript callback pattern. You can also use the Observer pattern.
We can create a Observable service with a subscribe method. This method will accept a function from the subscribing controller(s).
Observable.$inject = ['$http'];
function Observable($http) {
this.$http = $http;
this.subscribers = [];
}
Observable.prototype.subscribe = function(fn) {
this.subscribers.push(fn);
}
Observable.prototype.getSomething = function() {
var self = this;
this.$http.get('/something').then(function() {
self.subscribers.forEach(function(fn) { fn() })));
}
app.service('observable', Observable);
Then in your controller you can subscribe to the updates:
ObserverController = ['observale'];
function ObserverController(observable) {
observable.subscribe(this.callback);
}
ObserverController.prototype.callback = function() {
// this is called from the Observable service
}
app.controller('ObserverController', ObserverController);
In your controller you are explicitly subscribing to get updates from the Observable service. Every time the getSomething method in the service is called, the functions that have previously been passed to the subscribe method will be called.
Typically an Observable will pass an object/value to the Observer.

How do we mock our services to test the controller?

I'm new to angular js and trying to test a controller.
I have service like this:
angular.module('test'){
service.getAllServices = function()
{
var fullPath = url
var deferred = $q.defer()
if(!$.isEmptyObject(service.allServices))
deferred.resolve(service.allServices)
else
$http.get(fullPath)
.success(function(data,status,headers,config){
service.allServices = data
deferred.resolve(service.allServices)
})
.error(deferred.object)
return deferred.promise;
}
})
And the controller looks like this:
angular.module('test')
.controller('MainCtrl', function ($scope,$http,HttpService) {
$scope.awesomeThings = [
"Loading"
];
$scope.loadServices = function()
{
HttpService.getAllServices().then(function(result){
// console.log("Success")
$scope.awesomeThings = result.entities
},function(reason){
console.log(reason)
})
};
And I have a test case like this:
describe('Testing controller', function () {
// load the controller's module
beforeEach(module('test'));
var MainCtrl,
scope;
// Initialize the controller and a mock scope
beforeEach(inject(function ($controller, $rootScope,$q,HttpService) {
scope = $rootScope.$new();
var deferred = $q.defer();
var response = {
things: 'and stuff'
}
deferred.resolve(response);
spyOn(HttpService, 'getAllServices').andReturn(deferred.promise);
MainCtrl = $controller('MainCtrl', {
$scope: scope,
HttpService : HttpService
});
scope.loadServices();
}));
it('Controller is dummy, just gives back, what services gives back', function () {
expect(scope.awesomeThings).toEqual({
things: 'and stuff'
});
});
});
Even though I'm injecting the deferred object, looks like still my test case is failing, with this message:
Expected [ 'Loading' ] to equal { things : 'and stuff' }.
Where I'm making mistake?
You need to call digest to force the promise success function to run. You can do it in your test, or in your set up after the call to loadServices like so:
scope.loadServices();
scope.$digest();
Hope this helps.

Test a controller with success() and error ()

I'm trying to work out the best way to unit test success and error callbacks in controllers. I am able to mock out service methods, as long as the controller only uses the default $q functions such as 'then' (see the example below). I'm having an issue when the controller responds to a 'success' or 'error' promise. (Sorry if my terminology is not correct).
Here is an example controller \ service
var myControllers = angular.module('myControllers');
myControllers.controller('SimpleController', ['$scope', 'myService',
function ($scope, myService) {
var id = 1;
$scope.loadData = function () {
myService.get(id).then(function (response) {
$scope.data = response.data;
});
};
$scope.loadData2 = function () {
myService.get(id).success(function (response) {
$scope.data = response.data;
}).error(function(response) {
$scope.error = 'ERROR';
});
};
}]);
cocoApp.service('myService', [
'$http', function($http) {
function get(id) {
return $http.get('/api/' + id);
}
}
]);
I have the following test
'use strict';
describe('SimpleControllerTests', function () {
var scope;
var controller;
var getResponse = { data: 'this is a mocked response' };
beforeEach(angular.mock.module('myApp'));
beforeEach(angular.mock.inject(function($q, $controller, $rootScope, $routeParams){
scope = $rootScope;
var myServiceMock = {
get: function() {}
};
// setup a promise for the get
var getDeferred = $q.defer();
getDeferred.resolve(getResponse);
spyOn(myServiceMock, 'get').andReturn(getDeferred.promise);
controller = $controller('SimpleController', { $scope: scope, myService: myServiceMock });
}));
it('this tests works', function() {
scope.loadData();
expect(scope.data).toEqual(getResponse.data);
});
it('this doesnt work', function () {
scope.loadData2();
expect(scope.data).toEqual(getResponse.data);
});
});
The first test passes and the second fails with the error "TypeError: Object doesn't support property or method 'success'". I get that in this instance that getDeferred.promise
does not have a success function. Okay here is the question, what is a nice way to write this test so that I can test the 'success', 'error' & 'then' conditions of a mocked service ?
I'm starting to think that I should avoid the use of success() and error() in my controllers...
EDIT
So after thinking about this some more, and thanks to the detailed answer below, I've come to the conclusion that the handling the success and error callbacks in the controller is bad. As HackedByChinese mentions below success\error is syntactic sugar that is added by $http. So, in actual fact, by trying to handle success \ error I am letting $http concerns leak into my controller, which is exactly what I was trying to avoid by wrapping the $http calls in a service. The approach I'm going to take is to change the controller not to use success \ error:
myControllers.controller('SimpleController', ['$scope', 'myService',
function ($scope, myService) {
var id = 1;
$scope.loadData = function () {
myService.get(id).then(function (response) {
$scope.data = response.data;
}, function (response) {
$scope.error = 'ERROR';
});
};
}]);
This way I can test the error \ success conditions by calling resolve() and reject() on the deferred object:
'use strict';
describe('SimpleControllerTests', function () {
var scope;
var controller;
var getResponse = { data: 'this is a mocked response' };
var getDeferred;
var myServiceMock;
//mock Application to allow us to inject our own dependencies
beforeEach(angular.mock.module('myApp'));
//mock the controller for the same reason and include $rootScope and $controller
beforeEach(angular.mock.inject(function($q, $controller, $rootScope, $routeParams) {
scope = $rootScope;
myServiceMock = {
get: function() {}
};
// setup a promise for the get
getDeferred = $q.defer();
spyOn(myServiceMock, 'get').andReturn(getDeferred.promise);
controller = $controller('SimpleController', { $scope: scope, myService: myServiceMock });
}));
it('should set some data on the scope when successful', function () {
getDeferred.resolve(getResponse);
scope.loadData();
scope.$apply();
expect(myServiceMock.get).toHaveBeenCalled();
expect(scope.data).toEqual(getResponse.data);
});
it('should do something else when unsuccessful', function () {
getDeferred.reject(getResponse);
scope.loadData();
scope.$apply();
expect(myServiceMock.get).toHaveBeenCalled();
expect(scope.error).toEqual('ERROR');
});
});
As someone had mentioned in a deleted answer, success and error are syntactic sugar added by $http so they aren't there when you create your own promise. You have two options:
1 - Don't mock the service and use $httpBackend to setup expectations and flush
The idea is to let your myService act like it normally would without knowing it's being tested. $httpBackend will let you set up expectations and responses, and flush them so you can complete your tests synchronously. $http won't be any wiser and the promise it returns will look and function like a real one. This option is good if you have simple tests with few HTTP expectations.
'use strict';
describe('SimpleControllerTests', function () {
var scope;
var expectedResponse = { name: 'this is a mocked response' };
var $httpBackend, $controller;
beforeEach(module('myApp'));
beforeEach(inject(function(_$rootScope_, _$controller_, _$httpBackend_){
// the underscores are a convention ng understands, just helps us differentiate parameters from variables
$controller = _$controller_;
$httpBackend = _$httpBackend_;
scope = _$rootScope_;
}));
// makes sure all expected requests are made by the time the test ends
afterEach(function() {
$httpBackend.verifyNoOutstandingExpectation();
$httpBackend.verifyNoOutstandingRequest();
});
describe('should load data successfully', function() {
beforeEach(function() {
$httpBackend.expectGET('/api/1').response(expectedResponse);
$controller('SimpleController', { $scope: scope });
// causes the http requests which will be issued by myService to be completed synchronously, and thus will process the fake response we defined above with the expectGET
$httpBackend.flush();
});
it('using loadData()', function() {
scope.loadData();
expect(scope.data).toEqual(expectedResponse);
});
it('using loadData2()', function () {
scope.loadData2();
expect(scope.data).toEqual(expectedResponse);
});
});
describe('should fail to load data', function() {
beforeEach(function() {
$httpBackend.expectGET('/api/1').response(500); // return 500 - Server Error
$controller('SimpleController', { $scope: scope });
$httpBackend.flush();
});
it('using loadData()', function() {
scope.loadData();
expect(scope.error).toEqual('ERROR');
});
it('using loadData2()', function () {
scope.loadData2();
expect(scope.error).toEqual('ERROR');
});
});
});
2 - Return a fully-mocked promise
If the thing you're testing has complicated dependencies and all the set-up is a headache, you may still want to mock the services and the calls themselves as you have attempted. The difference is that you'll want to fully mock promise. The downside of this can be creating all the possible mock promises, however you could make that easier by creating your own function for creating these objects.
The reason this works is because we pretend that it resolves by invoking the handlers provided by success, error, or then immediately, causing it to complete synchronously.
'use strict';
describe('SimpleControllerTests', function () {
var scope;
var expectedResponse = { name: 'this is a mocked response' };
var $controller, _mockMyService, _mockPromise = null;
beforeEach(module('myApp'));
beforeEach(inject(function(_$rootScope_, _$controller_){
$controller = _$controller_;
scope = _$rootScope_;
_mockMyService = {
get: function() {
return _mockPromise;
}
};
}));
describe('should load data successfully', function() {
beforeEach(function() {
_mockPromise = {
then: function(successFn) {
successFn(expectedResponse);
},
success: function(fn) {
fn(expectedResponse);
}
};
$controller('SimpleController', { $scope: scope, myService: _mockMyService });
});
it('using loadData()', function() {
scope.loadData();
expect(scope.data).toEqual(expectedResponse);
});
it('using loadData2()', function () {
scope.loadData2();
expect(scope.data).toEqual(expectedResponse);
});
});
describe('should fail to load data', function() {
beforeEach(function() {
_mockPromise = {
then: function(successFn, errorFn) {
errorFn();
},
error: function(fn) {
fn();
}
};
$controller('SimpleController', { $scope: scope, myService: _mockMyService });
});
it('using loadData()', function() {
scope.loadData();
expect(scope.error).toEqual("ERROR");
});
it('using loadData2()', function () {
scope.loadData2();
expect(scope.error).toEqual("ERROR");
});
});
});
I rarely go for option 2, even in big applications.
For what it's worth, your loadData and loadData2 http handlers have an error. They reference response.data but the handlers will be called with the parsed response data directly, not the response object (so it should be data instead of response.data).
Don't mix concerns!
Using $httpBackend inside a controller is a bad Idea since you are mixing concerns inside your Test. Whether you retrieve data from an Endpoint or not is not a concern of the Controller, is a concern of the DataService you are calling.
You can see this more clearly if you change the Endpoint Url inside the service you will then have to modify both tests: the service Test and the Controller Test.
Also as previously mentioned, the use of success and error are syntactic sugar and we should stick to the use of then and catch. But in reality you may find yourself in the need of testing "legacy" code. So for that I'm using this function:
function generatePromiseMock(resolve, reject) {
var promise;
if(resolve) {
promise = q.when({data: resolve});
} else if (reject){
promise = q.reject({data: reject});
} else {
throw new Error('You need to provide an argument');
}
promise.success = function(fn){
return q.when(fn(resolve));
};
promise.error = function(fn) {
return q.when(fn(reject));
};
return promise;
}
By calling this function you will get a true promise that respond to then and catch methods when you need to and will also work for the success or error callbacks. Note that the success and error returns a promise itself so it will work with chained then methods.
(NOTE: On the 4th and 6th line the function returns resolve and reject values inside the data property of an object. This is to mock the Behavior of $http since it returns the data, http Status etc.)
Yes, do not use $httpbackend in your controller, because we don't need to make real requests, you just need to make sure that one unit is doing it's job exactly as expected, have a look on this simple controller tests, it's easy to understand
/**
* #description Tests for adminEmployeeCtrl controller
*/
(function () {
"use strict";
describe('Controller: adminEmployeeCtrl ', function () {
/* jshint -W109 */
var $q, $scope, $controller;
var empService;
var errorResponse = 'Not found';
var employeesResponse = [
{id:1,name:'mohammed' },
{id:2,name:'ramadan' }
];
beforeEach(module(
'loadRequiredModules'
));
beforeEach(inject(function (_$q_,
_$controller_,
_$rootScope_,
_empService_) {
$q = _$q_;
$controller = _$controller_;
$scope = _$rootScope_.$new();
empService = _empService_;
}));
function successSpies(){
spyOn(empService, 'findEmployee').and.callFake(function () {
var deferred = $q.defer();
deferred.resolve(employeesResponse);
return deferred.promise;
// shortcut can be one line
// return $q.resolve(employeesResponse);
});
}
function rejectedSpies(){
spyOn(empService, 'findEmployee').and.callFake(function () {
var deferred = $q.defer();
deferred.reject(errorResponse);
return deferred.promise;
// shortcut can be one line
// return $q.reject(errorResponse);
});
}
function initController(){
$controller('adminEmployeeCtrl', {
$scope: $scope,
empService: empService
});
}
describe('Success controller initialization', function(){
beforeEach(function(){
successSpies();
initController();
});
it('should findData by calling findEmployee',function(){
$scope.findData();
// calling $apply to resolve deferred promises we made in the spies
$scope.$apply();
expect($scope.loadingEmployee).toEqual(false);
expect($scope.allEmployees).toEqual(employeesResponse);
});
});
describe('handle controller initialization errors', function(){
beforeEach(function(){
rejectedSpies();
initController();
});
it('should handle error when calling findEmployee', function(){
$scope.findData();
$scope.$apply();
// your error expectations
});
});
});
}());

How do I mock the result in a $http.get promise when testing my AngularJS controller?

After much reading, it seems that the recommended way to call a web service from an AngularJS controller is to use a factory and return a promise from that.
Here I have a simple factory which calls a sample API.
myApp.factory('MyFactory', ['$http',function($http) {
var people = {
requestPeople: function(x) {
var url = 'js/test.json';
return $http.get(url);
}
};
return people;
}]);
And this is how I call it in the controller
myApp.controller('MyCtrl1', ['$scope', 'MyFactory', function ($scope, MyFactory) {
MyFactory.requestPeople(22).then(function(result) {
$scope.peopleList = result;
});
}]);
While it works fine, I would like to be able to mock the result that is passed in when then is called. Is this possible?
My attempt so far has produced nothing. This is my attempt:
//Fake service
var mockService = {
requestPeople: function () {
return {
then: function () {
return {"one":"three"};
}
}
}
};
//Some setup
beforeEach(module('myApp.controllers'));
var ctrl, scope;
beforeEach(inject(function ($rootScope, $controller) {
scope = $rootScope.$new();
ctrl = $controller('MyCtrl1', { $scope: scope, MyFactory: mockService });
}));
//Test
it('Event Types Empty should default to false', inject(function () {
expect(scope.peopleList.one).toBe('three');
}));
The error that I get when running this in karma runner, is
TypeError: 'undefined' is not an object (evaluating 'scope.peopleList.one')
How can I get this test working with my mocked data?
I don't think $httpBackend is what you're after here, you want the whole factory to be mocked without it having a dependency on $http?
Take a look at $q, in particular the code sample under the Testing header. Your issue might be resolved with code that looks like this:
'use strict';
describe('mocking the factory response', function () {
beforeEach(module('myApp.controllers'));
var scope, fakeFactory, controller, q, deferred;
//Prepare the fake factory
beforeEach(function () {
fakeFactory = {
requestPeople: function () {
deferred = q.defer();
// Place the fake return object here
deferred.resolve({ "one": "three" });
return deferred.promise;
}
};
spyOn(fakeFactory, 'requestPeople').andCallThrough();
});
//Inject fake factory into controller
beforeEach(inject(function ($rootScope, $controller, $q) {
scope = $rootScope.$new();
q = $q;
controller = $controller('MyCtrl1', { $scope: scope, MyFactory: fakeFactory });
}));
it('The peopleList object is not defined yet', function () {
// Before $apply is called the promise hasn't resolved
expect(scope.peopleList).not.toBeDefined();
});
it('Applying the scope causes it to be defined', function () {
// This propagates the changes to the models
// This happens itself when you're on a web page, but not in a unit test framework
scope.$apply();
expect(scope.peopleList).toBeDefined();
});
it('Ensure that the method was invoked', function () {
scope.$apply();
expect(fakeFactory.requestPeople).toHaveBeenCalled();
});
it('Check the value returned', function () {
scope.$apply();
expect(scope.peopleList).toBe({ "one": "three" });
});
});
I've added some tests around what $apply does, I didn't know that until I started playing with this!
Gog

Categories

Resources