I'm running a single test on my controller to determine if it's properly defined but I keep getting a TypeError: undefined on the controller object. Here's the complete error:
Search Controller
should have the controller defined <<< FAILURE!
* TypeError: 'undefined' is not an object (evaluating 'myMenuDataLoad.then')
* Expected undefined to be defined.
And here is the controller to be tested:
myAppControllers.controller('VisibilitySearchController', ['$scope', 'headerService', 'menuService', 'navigationService', function($scope, headerService, menuService, navigationService ){
headerService.setTitle('My title');
var myMenuDataLoad = menuService.loadData('partials/common/components/menu-bar/json/menu-bar.json');
myMenuDataLoad.then(function(dataResult){
menuService.setData(dataResult.data);
});
var myNavDataLoad = navigationService.loadData('partials/common/components/navigation-bar/json/navigation-bar.json');
myNavDataLoad.then(function(dataResult){
navigationService.setData(dataResult.data);
});
}]);
I've initialized the controller by passing it everything it needs in its parameters i.e. scope, headerService, menuService and navigationService - I mock these services using the jasmine.createSpyObj method and pass in all the relevant methods ( the ones used on the controller ):
// Mock our services
beforeEach(function() {
// Methods are accepted as the 2nd second parameter
headerService = jasmine.createSpyObj('headerService', ['setTitle']);
module(function($provide) {
$provide.value('headerService', headerService);
});
menuService = jasmine.createSpyObj('menuService', ['loadData', 'setData']);
module(function($provide) {
$provide.value('menuService', menuService);
});
navigationService = jasmine.createSpyObj('navigationService', ['loadData', 'setData']);
module(function($provide) {
$provide.value('navigationService', navigationService);
});
});
And the actual initialization of the controller happens here:
beforeEach(inject(function($rootScope, $injector, $controller, _headerService_, _menuService_, _navigationService_) {
scope = $rootScope.$new();
// Instantiate the controller
searchController = $controller('VisibilitySearchController', {
$scope : scope,
headerService : headerService,
menuService : menuService,
navigationService : navigationService
});
}));
So what am I doing wrong here? Why isn't the test (see below) passing?
it("should have the controller defined", function() {
expect(searchController).toBeDefined();
});
Have I mocked the services correctly? What action needs to be done on a local controller variable in order to properly initialize them and the methods they are used in?
Thanks!
UPDATE
I've looked further into this but am unfortunately still receiving the same undefined error. When you create a mock object of a service do you have to provide that service with all of its dependencies and methods you make use of? For example:
menuService = jasmine.createSpyObj('menuService', ['$parse','$q', 'dataService', 'loadData', 'then']);
module(function($provide) {
$provide.value('menuService', menuService);
});
Here when I create the mock object I provide it with all the dependencies it would expect plus I added in two functions that I make use of in the controller.
So how do I go about mocking a function in a mocked object? I tried this but I'm still getting the same error:
menuService.loadData = jasmine.createSpy( 'loadData()' ).andReturn( data );
As mentioned in the comment your menuService.loadData() will always return undefined so evaluating expression myMenuDataLoad.then will always fail as mentioned in the error. What you must do is to provide an implementation of menuService.loadData which will return a promise. You can do the mocking the way you did it in case you want these method to be called but you don't rely on any return value of it. If you need the method to return something you can do define menuService this way:
var menuService = {
loadData: function() {
var deferred = $q.defer();
var data = []; //put any data you need here to be returned within the promise
deferred.resolve{data);
return deferred.promise;
}
}
module(function($provide) {
$provide.value('menuService', menuService);
});
You will need instance of $q which you can get in your inject call similarly to $rootScope, $injector etc.
In case you wanted to spy on menuService.load function you can do it this way:
spyOn(menuService, "loadData").andCallThrough()
That will keep your mocked implementation of the method but still allow you to assert it was called etc. I don't think you need it.
Related
I have a controller that calls a Service which is a wrapper for a Resource. Like this:
app.factory("Service", ["$resource", function ($resource) {
return $resource("/api/Service/get");
}]);
Return value of the service's method is assigned to a variable within the controller. Normally, the variable is of type Resource and it contains a promise. When the promise is resolved, the variable is populated with all values returned from the backend.
I track then on the promise in order to modify the model received from the backend. Like so:
this.model = Service.get();
this.model.$promise.then(function(data) {
// do something with data
});
I need to test the value of the resulting model variable in my controller.
The only way I found to do this, is to use $httpBackend with a real implementation of my Service. However, this is ugly because then, testing my controller, I have to pass request path "api/Service/get" to the httpBackend.when() in order for it to respond with some value.
An excerpt form my test:
// call Controller
$httpBackend.when('GET', '/api/Service/get').respond(someData);
$httpBackend.flush();
expect(scope.model.property).toBe(null);
This seems and feels utterly wrong. The whole point of using a separate service to deal with resource is for the controller to not know anything about the url and http method name. So what should I do?
In other words, what I want to test is that then gets called and does what I need it to do.
I guess I could probably create a separate service that gets called in then and do what I need to do with the model but it feels a bit overkill if all I want to do is, for example, set one field to null depending on a simple condition.
You are correct, you shouldn't have to use $httpBackend unless you are using $http in the controller you are testing.
As you wrote, the controller shouldn't need to know anything about the implementation of Service. What the controller knows is that Service has a get method that returns an object with a $promise property that is a promise.
What you want to do is to use a fake implementation of Service in your test. There are multiple ways to do this via mocks, spies, stubs etc, depending on your use case and which testing framework(s) you are using.
One way is to create a fake implementation like this:
var Service = {
get: function() {
deferred = $q.defer();
return {
$promise: deferred.promise
};
}
};
You want to be able to access deferred from the tests, so you can either resolve or reject the promise based on what you want to test.
Full setup:
var $rootScope,
scope,
createController,
$q,
deferred;
var Service = {
get: function() {
deferred = $q.defer();
return {
$promise: deferred.promise
};
}
};
beforeEach(function() {
module('App');
inject(function(_$rootScope_, $controller, _$q_) {
$rootScope = _$rootScope_;
scope = $rootScope.$new();
createController = function() {
$controller('MyController', {
$scope: scope,
Service: Service
});
};
$q = _$q_;
});
});
Controller implementation:
app.controller('MyController', function($scope, Service) {
$scope.property = false;
$scope.model = Service.get();
$scope.model.$promise.then(function(data) {
if (data) {
$scope.property = true;
}
});
});
You can then spy on the fake implementation to assert that it is called correctly.
Example with Jasmine:
spyOn(Service, 'get').and.callThrough();
You need and.callThrough() or the call will be interrupted and your fake implementation will not be used.
You now have full control by manually creating the controller, resolving the promise and triggering the digest loop and can test the different states:
it('Should work', function() {
spyOn(Service, 'get').and.callThrough();
expect(Service.get).not.toHaveBeenCalled();
createController();
expect(Service.get).toHaveBeenCalled();
expect(scope.property).toBeFalsy();
deferred.resolve('some data');
$rootScope.$digest();
expect(scope.property).toBeTruthy();
});
Demo: http://plnkr.co/edit/th2pLWdVa8AZWOyecWOF?p=preview
I have seen a set of duplicates for this question but was unable to solve the issue.
I have a controller and during the controller initialization, fetchtemplate() is getting called first and then my mock fetchtemplate() is getting called.
How do I stop the actual(controller) fetchtemplate() getting called during the controller initialization? My intention is to mock the function fetchtemplate() in my spec.Please have a look at my spec -
describe("...",function(){
beforeEach(inject(function($controller,...) {
scope = $rootScope.$new();
this.init = function() {
$controller('ChangeControlCreateController', {
$scope: scope
});
}
}));
describe('Function', function() {
it("-- check for trueness",function(){
this.init() ; //Initialization of the controller
spyOn(scope,'fetchtemplate').and.callFake(function() {
return 101;
});
var fakeResponse = scope.fetchtemplate();
expect(scope.fetchtemplate).toHaveBeenCalled();
expect(fakeResponse).toEqual(101);
});
});
});
I have tried placing the spyOn before the this.init() which gave error as the fetchtemplate() doesn't exist at that time to spyOn.
My controller code structure looks like -
angular.module('...', [...])
.controller('ChangeControlCreateController', ["$scope"...,
function ChangeControlCreateController($scope,...) {
$scope.fetchtemplate = function() {
console.log("controller's function");
...
};
$scope.fetchtemplate();
});
The result what I am getting is - First the console item "controller's function" and then the spec is executing with mock function. I want the mock function to execute without the controller's function to execute
So if I understand correctly you are doing some call to a function that is doing something you want to prevent for test purposes. Probably an http call or some thing of the sort ?
Whatever it is doing the proper way to handle something like that is usually to put that method inside a service instead and then to spy on that service method. Here is an example of test if the service is TemplateService :
describe("...",function(){
var $controller, scope, TemplateService, YourController;
beforeEach(inject(function(_$controller_, _TemplateService_, ...) {
scope = $rootScope.$new();
$controller = _$controller_;
TemplateService = _TemplateService_;
}
it("-- check for trueness",function(){
spyOn(TemplateService,'fetchTemplate').and.returnValue('101');
YourController = $controller('YourController');
expect(...);
});
});
I hope that's helpful
I'm using jasmine as testframework and I've the following Controller I want to test. And I allways have a Init() function where I place my initialization calls for this Controller.
Now I want to test if the Init function was called when the controller was initialized.
function UnitTestsCtrl() {
var that = this;
this.Init();
}
UnitTestsCtrl.prototype.Init = function() {
var that = this;
//Some more Stuff
}
angular.module("unitTestsCtrl", [])
.controller("unitTestsCtrl", UnitTestsCtrl);
But I was not able to check if the Init function was called on controller creation. I know my example doesn't work because the spy is set on the Init function after creation.
describe('Tests Controller: "UnitTestsCtrl"', function() {
var ctrl;
beforeEach(function() {
module('app.main');
inject(function ($controller) {
ctrl = $controller('unitTestsCtrl', {});
});
});
it('Init was called on Controller initialize', function () {
//thats not working
spyOn(ctrl, 'Init');
expect(ctrl.Init).toHaveBeenCalled();
});
});
Solution:
Create the spy on the Original Prototype in the beforeEach function
beforeEach(function() {
module('app.main');
spyOn(UnitTestsCtrl.prototype, 'Init');
inject(function ($controller) {
ctrl = $controller('unitTestsCtrl', {});
});
});
it('Init was called on Controller initialize', function () {
expect(ctrl.Init).toHaveBeenCalled();
});
The way it is, you cannot and you really do not need to as well. The reason you cannot is because you are calling init() on the controller constructor, i.e on instantiation, which happens when you call $controller service to instantiate the controller in your test. So you are setting up spy too late. You probably do not need to, because if the controller is instantiated init method would have been called for sure. But how ever if you are making any specific service/dependency calls inside init, you can spy on those mocks and set up expectations.
Your expectation says: Service Call executed so create a spy for that service and set up expectation.
example:
var myService = jasmine.createSpyObj('myService', ['someCall']);
myService.someCall.and.returnValue($q.when(someObj));
//...
ctrl = $controller('unitTestsCtrl', {'myService':myService});
and set the expectation on the method someCall of myService.
expect(myService.someCall).toHaveBeenCalled();
If you really want to spy on init, then you would need to have access to UnitTestsCtrl constructor in the spec and you would need to set spy on its prototype method init before instantiating.
I want to test that the following function is in fact called upon the initialization of this controller using jasmine. It seems like using a spy is the way to go, It just isn't working as I'd expect when I put the expectation for it to have been called in an 'it' block. I'm wondering if there is a special way to check if something was called when it wasn't called within a scope function, but just in the controller itself.
App.controller('aCtrl', [ '$scope', function($scope){
$scope.loadResponses = function(){
//do something
}
$scope.loadResponses();
}]);
//spec file
describe('test spec', function(){
beforeEach(
//rootscope assigned to scope, scope injected into controller, controller instantiation.. the expected stuff
spyOn(scope, 'loadResponses');
);
it('should ensure that scope.loadResponses was called upon instantiation of the controller', function(){
expect(scope.loadResponses).toHaveBeenCalled();
});
});
You need to initialise the controller yourself with the scope you've created. The problem is, that you need to restructure your code. You can't spy on a non-existing function, but you need to spyOn before the function gets called.
$scope.loadResponses = function(){
//do something
}
// <-- You would need your spy attached here
$scope.loadResponses();
Since you cannot do that, you need to make the $scope.loadResponses() call elsewhere.
The code that would successfully spy on a scoped function is this:
var scope;
beforeEach(inject(function($controller, $rootScope) {
scope = $rootScope.$new();
$controller('aCtrl', {$scope: scope});
scope.$digest();
}));
it("should have been called", function() {
spyOn(scope, "loadResponses");
scope.doTheStuffThatMakedLoadResponsesCalled();
expect(scope.loadResponses).toHaveBeenCalled();
});
Setting the spy before controller instantiation (in the beforeEach) is the way to test controller functions that execute upon instantiation.
EDIT: There is more to it. As a comment points out, the function doesn't exist at the time of ctrl instantiation. To spy on that call you need to assign an arbitrary function to the variable (in this case you assign scope.getResponses to an empty function) in your setup block AFTER you have scope, but BEFORE you instantiate the controller. Then you need to write the spy (again in your setup block and BEFORE ctrl instantiation), and finally you can instantiate the controller and expect a call to have been made to that function. Sorry for the crappy answer initially
The only way I have found to test this type of scenarios is moving the method to be tested to a separate dependency, then inject it in the controller, and provide a fake in the tests instead.
Here is a very basic working example:
angular.module('test', [])
.factory('loadResponses', function() {
return function() {
//do something
}
})
.controller('aCtrl', ['$scope', 'loadResponses', function($scope, loadResponses) {
$scope.loadResponses = loadResponses;
$scope.loadResponses();
}]);
describe('test spec', function(){
var scope;
var loadResponsesInvoked = false;
var fakeLoadResponses = function () {
loadResponsesInvoked = true;
}
beforeEach(function () {
module('test', function($provide) {
$provide.value('loadResponses', fakeLoadResponses)
});
inject(function($controller, $rootScope) {
scope = $rootScope.$new();
$controller('aCtrl', { $scope: scope });
});
});
it('should ensure that scope.loadResponses was called upon instantiation of the controller', function () {
expect(loadResponsesInvoked).toBeTruthy();
});
});
For real world code you will probably need extra work (for example, you may not always want to fake the loadResponses method), but you get the idea.
Also, here is a nice article that explains how to create fake dependencies that actually use Jasmine spies: Mocking Dependencies in AngularJS Tests
EDIT: Here is an alternative way, that uses $provide.delegate and does not replace the original method:
describe('test spec', function(){
var scope, loadResponses;
var loadResponsesInvoked = false;
beforeEach(function () {
var loadResponsesDecorator = function ($delegate) {
loadResponsesInvoked = true;
return $delegate;
}
module('test', function($provide) {
$provide.decorator('loadResponses', loadResponsesDecorator);
});
inject(function($controller, $rootScope) {
scope = $rootScope.$new();
$controller('aCtrl', { $scope: scope });
});
});
it('should ensure that scope.loadResponses was called upon instantiation of the controller', function () {
expect(loadResponsesInvoked).toBeTruthy();
});
});
I didn't quite understand any of the answers above.
the method I often use - don't test it, instead test the output it makes..
you have not specified what loadResponses actually does.. but lets say it puts something on scope - so test existence of that..
BTW - I myself asked a similar question but on an isolated scope
angular - how to test directive with isolatedScope load?
if you still want to spy - on an unisolated scope, you could definitely use a technique..
for example, change your code to be
if ( !$scope.loadResponses ){
$scope.loadResponses = function(){}
}
$scope.loadResponses();
This way you will be able to define the spy before initializing the controller.
Another way, is like PSL suggested in the comments - move loadResponses to a service, spy on that and check it has been called.
However, as mentioned, this won't work on an isolated scope.. and so the method of testing the output of it is the only one I really recommend as it answers both scenarios.
I've been creating an angularjs framework for an application I'm planning to write. At the moment I'm working on a sample application, I'm documenting as I go in a tutorial so that I have everything I did in one place.
I'm currently trying to create unit tests using karma and jasmine for the modal dialog I'm presenting. This modal dialog is created using the $dialog service from angular-bootstrap. This dialog I think is using a promise to pass data into the dialog controller, and I'd like to resolve that promise so I can check in my unit test that the data that has been passed in is as expected. I'm having a little difficulty in working out how to resolve that, I see examples using either scope.$apply or scope.$digest, neither appear to work and to be frank I don't quite understand what it's doing. I'm concerned that in the unit test I have assigned this promise to a variable, and perhaps that it won't resolve once assigned to a variable. I see mention that this "resolve" parameter is similar to the resolve on a route, but so far that hasn't helped me, and I'm not 100% sure that it's really a promise at all.
I'm looking both for something that makes it work, but also an explanation of why that works.
The controller I'm seeking to test looks like this:
.controller( 'ClubCtrl', function ClubController( $scope, ClubRes, $dialog ) {
$scope.clubs = ClubRes.query();
/* this is called from a button, which passes one of the clubs from $scope.clubs */
$scope.editClub = function(club) {
$scope.myDialog = $dialog.dialog({dialogFade: false, resolve: {club: function(){return angular.copy(club);}}});
$scope.myDialog.open('club/club_edit.tpl.html', 'ClubEditCtrl').then(function(result){
if (result === 'cancel'){}
else {
$scope.clubs = ClubRes.query();
}
});
};
})
The unit test I'm trying to get working at this point is aiming to mock out the whole dialog, and to check that the dialog has been called with the correct input parameters:
describe( 'Base club controller', function() {
var scope, httpBackend;
//mock Application to allow us to inject our own dependencies
beforeEach(angular.mock.module('league'));
//mock the controller for the same reason and include $rootScope and $controller
beforeEach(angular.mock.inject(function($rootScope, $controller, _$httpBackend_ ){
//create an empty scope
scope = $rootScope.$new();
// setup a mock for the resource - instead of calling the server always return a pre-canned response
httpBackend = _$httpBackend_;
httpBackend.when('GET', '../clubs.json').respond([
{"contact_officer":"Officer 1","created_at":"2012-02-02T00:00:00Z","date_created":"2012-01-01T00:00:00Z","id":1,"name":"Club 1","updated_at":"2012-03-03T00:00:00Z"},
{"contact_officer":"Officer 2","created_at":"2012-02-02T00:00:00Z","date_created":"2012-01-01T00:00:00Z","id":2,"name":"Club 2","updated_at":"2012-03-03T00:00:00Z"}]);
// setup a mock for the dialog - when called it returns the value that was input when it was instantiated
scope.fakeDialog = {
parameters: null,
response: null,
template: null,
controller: null,
dialog: function(parameters) {
this.parameters = parameters;
return this;
},
open: function(template, controller) {
this.template = template;
this.controller = controller;
return this;
},
then: function(callBack){
callBack(this.response);
}
};
//declare the controller and inject our empty scope
$controller('ClubCtrl', {$scope: scope, $dialog: scope.fakeDialog});
}));
it('Calls edit on first row', function() {
// check nothing set beforehand
expect(scope.fakeDialog.parameters).toBe(null);
expect(scope.fakeDialog.template).toBe(null);
expect(scope.fakeDialog.controller).toBe(null);
// call edit
scope.editClub(scope.clubs[0]);
scope.$digest();
httpBackend.flush();
// expect stuff to have happened
expect(scope.fakeDialog.parameters.club.name).toBe('Club 1');
expect(scope.fakeDialog.template).toBe('club/club_edit.tpl.html');
expect(scope.fakeDialog.controller).toBe('ClubEditCtrl');
});
});
What I'm actually getting in console.log(scope.fakeDialog.parameters) is:
Object{dialogFade: false, resolve: Object{club: function (){ ... }}}
So my club is buried inside "resolve: Object......", which I think is a promise. I think what I need is a way to trigger that to resolve - but I'm not sure what that is.
OK, no responses as yet, and I've had the time tonight to piece through it slowly.
The short answer is that the resolve parameter to a dialog isn't necessarily a promise (although I think it can be sometimes if you wish it to be). Since I haven't passed in a promise I can directly evaluate these functions to work out their results, although I thought I'd tried that before and it didn't work.
I've also spent some time looking at spyOn, and I can use that for some of the things I had my mock doing, so I'm tidying that up at the same time.
My working code is as follows. Firstly, the controller that's being tested:
.controller( 'ClubCtrl', function ClubController( $scope, ClubRes, $dialog ) {
$scope.clubs = ClubRes.query();
/* this is called from a button, which passes one of the clubs from $scope.clubs */
$scope.editClub = function(club) {
$scope.myDialog = $dialog.dialog({dialogFade: false, resolve: {club: function(){return angular.copy(club);}}});
$scope.myDialog.open('club/club_edit.tpl.html', 'ClubEditCtrl').then(function(result){
if (result === 'cancel'){}
else {
$scope.clubs = ClubRes.query();
}
});
};
})
Then, the test code that tests that:
describe( 'Base club controller', function() {
var scope, httpBackend;
//mock Application to allow us to inject our own dependencies
beforeEach(angular.mock.module('league'));
//mock the controller for the same reason and include $rootScope and $controller
beforeEach(angular.mock.inject(function($rootScope, $controller, _$httpBackend_ ){
//create an empty scope
scope = $rootScope.$new();
// setup a mock for the resource - instead of calling the server always return a pre-canned response
httpBackend = _$httpBackend_;
httpBackend.when('GET', '../clubs.json').respond([
{"contact_officer":"Officer 1","created_at":"2012-02-02T00:00:00Z","date_created":"2012-01-01T00:00:00Z","id":1,"name":"Club 1","updated_at":"2012-03-03T00:00:00Z"},
{"contact_officer":"Officer 2","created_at":"2012-02-02T00:00:00Z","date_created":"2012-01-01T00:00:00Z","id":2,"name":"Club 2","updated_at":"2012-03-03T00:00:00Z"}]);
// setup a mock for the dialog - when called it returns the value that was input when it was instantiated
scope.fakeDialog = {
response: null,
club: null,
dialog: function(parameters) {
this.club = parameters.resolve.club();
return this;
},
open: function(template, controller) {
return this;
},
then: function(callBack){
callBack(this.response);
}
};
//declare the controller and inject our empty scope
$controller('ClubCtrl', {$scope: scope, $dialog: scope.fakeDialog});
}));
it('Calls edit on first row', function() {
// we expect the fakeDialog dialog and open methods to be called, so we spy on them to get the parameters
spyOn(scope.fakeDialog, "dialog").andCallThrough();
spyOn(scope.fakeDialog, "open").andCallThrough();
// call edit
scope.editClub(scope.clubs[0]);
scope.$digest();
httpBackend.flush();
// check parameters passed in
expect(scope.fakeDialog.dialog).toHaveBeenCalledWith({dialogFade: false, resolve: {club: jasmine.any(Function)}});
expect(scope.fakeDialog.club.contact_officer).toEqual('Contact Officer 1');
expect(scope.fakeDialog.open).toHaveBeenCalledWith('club/club_edit.tpl.html', 'ClubEditCtrl');
});
});
This seems to call the function and give the response into the club property on the fakeDialog object.