I am trying to test my controller by passing it a dummy version of a service. However, when karma runs it makes a call to the real service, which is specified in the controller definition (line 3) instead of the object I try to inject with beforeEach(inject(function(....)
Please help me identify what I am doing wrong.
//I have a code like the following.
angular.module('myApp')
.controller('memberSearch2Ctrl', ['$scope', 'PersonnelService', 'SavedSearchService', '$routeParams', '$log',
function memberSearch2Ctrl($scope, personnelApi, savedSearches, $routeParams, $log) {
// utilises PersonnelService to make ajax calls to get data from server....
}
]);
// real version of a service that makes ajax calls and returns real data
angular.module('myApp')
.service('PersonnelService', function($http, $q) {
//this.search(....)
});
// dummy version of the above, has the same functions like above just returns hardcoded json
angular.module('myApp')
.service('PersonnelServiceMock', function($http, $q) {
// returns hardcoded json for testing purpose
//this.search(....)
});
// heres my tests
describe('memberSearch2Ctrl', function() {
var ctrl, scope, personnelApiMock, savedSearchService;
beforeEach(module('myApp'));
beforeEach(inject(function($rootScope, $controller, PersonnelServiceMock, SavedSearchService, $log) {
personnelApiMock = PersonnelServiceMock; // this sets the PersonnelServiceMock correctly
console.log(JSON.stringify(PersonnelServiceMock)); // as I see in this line
console.log(JSON.stringify(SavedSearchService));
scope = $rootScope.$new();
ctrl = $controller('memberSearch2Ctrl', {
$scope: scope,
personnelApi: PersonnelServiceMock,
savedSearches: SavedSearchService,
$routeParams: {},
$log: $log
});
}));
iit('upon search $scope.searchResults = PersonnelService.searchPaged(...)', function() {
// however problem lies in the next line
scope.search(); // this calls PersonnelService.search insted of PersonnelServiceMock.search
// even when I have beforeEach(inject(function($rootScope, $controller, >>> PersonnelServiceMock <<<,
scope.$root.$digest();
var expected = personnelApiMock.searchPaged(null, null, null);
var actual = scope.searchResults;
expect(actual).toEqual(expected);
});
});
tried passing in $injector and having injector instantiate PersonnelServiceMock.
the console log says I am actually getting PersonnelServiceMock in return. But still it tries making the ajax call defined in the PersonnelService
beforeEach(inject(function($rootScope, $controller, $injector) {
personnelApiMock = $injector.get('PersonnelServiceMock');
savedSearchService = $injector.get('SavedSearchService');
log = $injector.get('$log');
console.log(JSON.stringify(personnelApiMock));
console.log('==========================================');
console.log(JSON.stringify(savedSearchService));
console.log('******************************************');
scope = $rootScope.$new();
ctrl = $controller('memberSearch2Ctrl', {
$scope: scope,
personnelApi: personnelApiMock,
savedSearches: savedSearchService,
$routeParams: {},
$log: log
});
}));
seems like what is specified in the call to ctrl = $controller('memberSearch2Ctrl', { ...}) is being ignored and what is specified in the controller definition (line ~3) is being used.
The issue is with this block of code:
ctrl = $controller('memberSearch2Ctrl', {
$scope: scope,
personnelApi: PersonnelServiceMock, <!---
savedSearches: SavedSearchService,
$routeParams: {},
$log: $log
});
Should be:
ctrl = $controller('memberSearch2Ctrl', {
$scope: scope,
PersonnelService: PersonnelServiceMock, <!---
savedSearches: SavedSearchService,
$routeParams: {},
$log: $log
});
Related
I am not clear how to use SpyOn in Unit Testing...
I have the following controller
(function () {
'use strict';
angular.module('otpConfigureDatasets').controller('otpActivityCardController', otpActivityCardController);
otpActivityCardController.$inject = ['$location', '$state', 'otpWebMapApp', 'otpWMDeltaTracker', 'otpWMStateCache', '$scope', '$timeout', 'otpActivityCardService', 'otpControlCenterData'];
function otpActivityCardController($location, $state, otpWebMapApp, otpWMDeltaTracker, otpWMStateCache, $scope, $timeout, otpActivityCardService, otpControlCenterData) {
var vm = this;
vm.cards = [];
otpActivityCardService.getActivityCards().then(function (resolve) {
vm.cards = resolve;
});
//.....Some code ....
})();
I need to test the GetActivityCards().then(function ...
I tried test it using the code below
'use strict';
describe('Test controller (activityCard) in Page MyDatasets', function() {
var MainCtrl, $state, scope, otpWebMapApp, otpWMDeltaTracker, otpWMStateCache, otpActivityCardService, otpControlCenterData;
var card;
beforeEach(function() {
module('otpConfigureDatasets');
});
beforeEach(inject(function ($controller, $rootScope, _$state_, _otpWebMapApp_, _otpWMDeltaTracker_, _otpWMStateCache_, _otpActivityCardService_, _otpControlCenterData_) {
scope = $rootScope.$new();
scope.$parent = { $parent: { menuParentGroupClick: function menuParentGroupClick() { } } };
MainCtrl = $controller('otpActivityCardController', {
$scope: scope
});
otpWebMapApp = _otpWebMapApp_;
otpWMDeltaTracker = _otpWMDeltaTracker_;
otpWMStateCache = _otpWMStateCache_;
otpActivityCardService = _otpActivityCardService_;
otpControlCenterData = otpControlCenterData;
}));
it('Test Function', function() {
spyOn(otpActivityCardService, 'getActivityCards');
expect(otpActivityCardService.getActivityCards).toHaveBeenCalled();
});
});
But I am getting this error:
Expected spy getActivityCards to have been called.
Error: Expected spy getActivityCards to have been called.
What is wrong?
You created a spy to the "getActivityCards" function, but you didn't call it in your test (unless you hid this line of code from the example).
When you create a Jasmine Spy to a function, you are only "watching" this function, you can check if it was called, you can mock the return values of it, you can check the parameters of a call to it, i.e, you can check a lot of things about the call history of the function, but you still need to explicity make a call to the function (or to a function in your controller that calls the spied function from it).
So you are spying the Service, and you are testing the Controller, your test should look something like:
it('Test Function', function() {
spyOn(otpActivityCardService, 'getActivityCards');
otpActivityCardService.getActivityCards();
expect(otpActivityCardService.getActivityCards).toHaveBeenCalled();
});
On a side note, to be more testable, your controller should encapsulate your service call in a function in your controller, like:
(function () {
'use strict';
angular.module('otpConfigureDatasets').controller('otpActivityCardController', otpActivityCardController);
otpActivityCardController.$inject = ['$location', '$state', 'otpWebMapApp', 'otpWMDeltaTracker', 'otpWMStateCache', '$scope', '$timeout', 'otpActivityCardService', 'otpControlCenterData'];
function otpActivityCardController($location, $state, otpWebMapApp, otpWMDeltaTracker, otpWMStateCache, $scope, $timeout, otpActivityCardService, otpControlCenterData) {
var vm = this;
vm.cards = [];
vm.getCards = function () {
otpActivityCardService.getActivityCards().then(function (resolve) {
vm.cards = resolve;
});
}
vm.getCards();
//.....Some code ....
})();
So you could create a test that really tested a function in your controller (because the way you are describing your test case, it really should be a Service test only)
it('Better test case', function() {
spyOn(otpActivityCardService, 'getActivityCards');
MainCtrl.getCards();
expect(otpActivityCardService.getActivityCards).toHaveBeenCalled();
});
I'm testing out on of my controllers. I keep getting
Error: Expected POST /auth/signup with different data
EXPECTED: {"username":"justin","firstName":"Justin","lastName":"Young","email":"xxxx#xxx.com","company":"5579d602ba9f26a414be5d57","url":"http://www.me.com","referrer":"me#me.com"}
It's completing the post to auth/signup as expected, but the data is empty? I'm passing in sampleUserResponse into the expectations so I don't get why it's not passing that data back as the response. What am I doing wrong?
My test:
'use strict';
(function() {
// Authentication controller Spec
describe('AdminItemController', function() {
// Initialize global variables
var controller,
scope,
$httpBackend,
$stateParams,
$location;
beforeEach(function() {
jasmine.addMatchers({
toEqualData: function(util, customEqualityTesters) {
return {
compare: function(actual, expected) {
return {
pass: angular.equals(actual, expected)
};
}
};
}
});
});
// Load the main application module
beforeEach(module(ApplicationConfiguration.applicationModuleName));
// The injector ignores leading and trailing underscores here (i.e. _$httpBackend_).
// This allows us to inject a service but then attach it to a variable
// with the same name as the service.
beforeEach(inject(function($controller, $rootScope, _$location_, _$stateParams_, _$httpBackend_) {
// Set a new global scope
scope = $rootScope.$new();
// Point global variables to injected services
$stateParams = _$stateParams_;
$httpBackend = _$httpBackend_;
$location = _$location_;
// Initialize the Authentication controller
controller = $controller('AdminItemController', {
$scope: scope,
$stateParams:$stateParams
});
$httpBackend.when('GET', 'modules/core/views/home.client.view.html').respond({});
}));
it('$scope.create() with valid form data should send a POST request with the form input values and then locate to new object URL', inject(function(Users) {
// Create a sample article object
var sampleUserPostData = new Users({
username: 'justin',
firstName:'Justin',
lastName:'Young',
email:'xxxx#xxx.com',
company:'5579d602ba9f26a414be5d57',
url:'http://www.me.com',
referrer:'me#me.com'
});
// Create a sample User response
var sampleUserResponse = new Users({
_id:'4579d602ba9f26a414be5d59',
username: 'justin',
firstName:'Justin',
lastName:'Young',
email:'xxxx#xxx.com',
company:'5579d602ba9f26a414be5d57',
url:'http://www.me.com',
referrer:'me#me.com'
});
// Fixture mock form input values
//scope.title = 'An User about MEAN';
//scope.content = 'MEAN rocks!';
// Set POST response
$httpBackend.expectPOST('/auth/signup', sampleUserPostData).respond(sampleUserResponse);
// Run controller functionality
scope.addPost();
$httpBackend.flush();
// Test form inputs are reset
//expect(scope.title).toEqual('');
//expect(scope.content).toEqual('');
// Test URL redirection after the User was created
//expect($location.path()).toBe('/admin/users/' + sampleUserResponse._id);
}));
});
}());
My Simplified Controller:
.controller('AdminItemController', ['$scope', '$http', '$location','apiResource','$stateParams', '$state','$log',
function($scope, $http, $location, apiResource, $stateParams, $state, $log) {
$scope.addPost = function() {
apiResource.save({api_resource:'auth', api_action: 'signup'},$scope.item).$promise.then(function(response){
$scope.$parent.users.push($scope.item);
});
};
}
])
The problem is in your controller, the params you send with the POST is $scope.item but in your test, you DO NOT set your $scope.item to be anything. Therefore, a POST with undefined params will be sent (because $scope.item is undefined). Moreover, in your test, you expect the params sent to equal to sampleUserPostData. Apparently it will fail because undefined !== sampleUserPostData. What you can do is just to set the scope.item = sampleUserPostData; before expectPOST and it will be fine.
Working fiddle.
The scenario is I have a ChildCtrl controller that inherits from BaseCtrl following this inheritance pattern:
angular.module('my-module', [])
.controller('BaseCtrl', function ($scope, frobnicate) {
console.log('BaseCtrl instantiated');
$scope.foo = frobnicate();
// do a bunch of stuff
})
.controller('ChildCtrl', function ($controller, $scope) {
$controller('BaseCtrl', {
$scope: $scope,
frobnicate: function () {
return 123;
}
});
});
Assuming BaseCtrl does a bunch of stuff and is already well tested, I want to test that ChildCtrl instantiates BaseCtrl with certain arguments. My initial thought was something along these lines:
describe("ChildCtrl", function () {
var BaseCtrl;
beforeEach(module('my-module'));
beforeEach(module(function($provide) {
BaseCtrl = jasmine.createSpy();
$provide.value('BaseCtrl', BaseCtrl);
}));
it("inherits from BaseCtrl", inject(function ($controller, $rootScope) {
$controller('ChildCtrl', { $scope: $rootScope.$new() });
expect(BaseCtrl).toHaveBeenCalled();
}));
});
However when I run the test the spy is never called and the console shows "BaseCtrl instantiated", indicating that $controller is using the actual controller instead of the instance I am providing with $provide.value().
What's the best way to test this?
So it looks like $controller doesn't search for controllers by name in the $provide.value() namespace. Instead you have to use the $controllerProvider.register() method, which is only accessible from the module.config() block. Fortunately it looks like there's a hook we can use to get access to $controllerProvider on the module under test.
The updated test code looks like:
describe("ChildCtrl", function () {
var BaseCtrl;
beforeEach(module('my-module', function ($controllerProvider) {
BaseCtrl = jasmine.createSpy();
BaseCtrl.$inject = ['$scope', 'frobnicate'];
$controllerProvider.register('BaseCtrl', BaseCtrl);
}));
beforeEach(inject(function ($controller, $rootScope) {
$controller('ChildCtrl', { $scope: $rootScope.$new() });
}));
it("inherits from BaseCtrl", inject(function ($controller, $rootScope) {
expect(BaseCtrl).toHaveBeenCalled();
}));
it("passes frobnicate() function to BaseCtrl that returns 123", function () {
var args = BaseCtrl.calls.argsFor(0);
var frobnicate = args[1];
expect(frobnicate()).toEqual(123);
});
});
I've a basic controller and inside I've a global function that is called from a different controller, which works absolutely fine in a running application.
Now does anybody know how to unit test this global function?
the cntrl it contains the function
apmstore.controller('HeaderCtrl', function(authentication, $rootScope, $scope, loginService){
// how to test this function called in the loginCtrl below
$rootScope.setDropDownStatus = function(visID, user, userImg) {
$scope.dropdown = visID;
$scope.user = user;
$scope.imgUrl = userImg;
};
});
loginCtrl
apmstore.controller('loginCtrl', function($scope, authentication, loginService,
$rootScope) {
authentication.isAuthenticated = false;
//Here is where its called, i want to unit test this
$rootScope.setDropDownStatus(false, null, null);
$scope.templates = [ {url : '/login'}, {url : '/config'} ];
$scope.login = function() {
loginService.login($scope);
}
});
Do I need to change the logic in HeaderCtrl to make it easier to test, i.e. decouple into a service/factory etc?
Anyone know how to tackle this? Thanks
Try this:
it('should call setDropDownStatus ', inject(function($rootScope, $controller, authentication, loginService) {
$scope = $rootScope.$new();
//fake the setDropDownStatus function as we don't care where this function is created
//We only care about whether this function is called.
$rootScope.setDropDownStatus = function(){
}
spyOn($rootScope, "setDropDownStatus");
////////
ctrl = $controller('loginCtrl', {
$scope: $scope,
authentication: authentication,
loginService: loginService,
$rootScope: $rootScope
});
//verify that this function is called with expected parameters.
expect($rootScope.setDropDownStatus).toHaveBeenCalledWith(false,null,null);
}));
DEMO
Yes to your last question. You shouldn't be defining a function in one controller and using it in another. Put it in a service and inject the service wherever needed.
I need to test a Controller that use $routeParams to define it action. Maybe it is a test problem, or is wrong the way I wrote the controller, so now I can't write a test.
Here is my controller
angular.module('cmmApp')
.controller('UserCtrl', function ($scope, $location, $routeParams, $ionicLoading, $ionicPopup, Userservice) {
//if a user id is set retrieve user information
if(typeof $routeParams.id !== 'undefined'){
var loader = $ionicLoading.show({content: "Retrieving user data"});
var user = Userservice.get({id: $routeParams.id}).$promise;
user.then(function(res){
console.log(res);
$scope.user = res.user;
loader.hide();
});
}
//else create an empty resource
else {
$scope.user = new Userservice;
}
});
Basically I only want to load a resource if an id is provided is $routeParams, else create an empty resource.
And here is the test:
'use strict';
describe('Controller: UserCtrl', function () {
// load the controller's module
beforeEach(module('cmmApp'));
var UserCtrl,
scope,
routeParams;
// Initialize the controller and a mock scope
beforeEach(inject(function ($controller, $rootScope, $routeParams, Userservice) {
scope = $rootScope.$new();
routeParams = $routeParams;
UserCtrl = $controller('UserCtrl', {
$scope: scope
});
console.log("routeParams");
routeParams = {id: 8490394};
console.warn(routeParams);
}));
it('should create an epmty user', function () {
console.log(scope.user);
expect(scope.user).toEqual({});
});
});
When I run grunt test karma reply:
Controller: UserCtrl should create an epmty user FAILED
Expected { } to equal { }.
Error: Expected { } to equal { }.
at null.<anonymous> (/Users/carlopasqualicchio/Sites/CMM_Ionic/test/spec/controllers/user.js:28:24)
I'm wonering how to tell karma to change $routeParams.idbefore a test to run, and assign a different value in two different test (I need to set it to null in the first, and to a value to the other).
Any help is appreciated.
Thanks, Matt.
You can pass a custom $routeParams object using the second argument of $controller (locals object), i.e. the same way you pass the $scope:
UserCtrl = $controller('UserCtrl', {
$scope: scope,
$routeParams: {id: '...'}
});