Jasmine Js - SpyOn Fakecall during the controller initialization - angularjs

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

Related

How does the createSpy work in Angular + Jasmine?

I made a simple demo of a factory and I am trying to test this using jasmine. I am able to run the test but I am using the spyOn method. I would rather use jasmine.createSpy or jasmine.createSpyObj to do the same test. Could someone help me to refactor my code so that uses these methods instead in my example?
http://plnkr.co/edit/zdfYdtWbnQz22nEbl6V8?p=preview
describe('value check',function(){
var $scope,
ctrl,
fac;
beforeEach(function(){
module('app');
});
beforeEach(inject(function($rootScope,$controller,appfactory) {
$scope = $rootScope.$new();
ctrl = $controller('cntrl', {$scope: $scope});
fac=appfactory;
spyOn(fac, 'setValue');
fac.setValue('test abc');
}));
it('test true value',function(){
expect(true).toBeTruthy()
})
it('check message value',function(){
expect($scope.message).toEqual(fac.getValue())
})
it("tracks that the spy was called", function() {
expect(fac.setValue).toHaveBeenCalled();
});
it("tracks all the arguments of its calls", function() {
expect(fac.setValue).toHaveBeenCalledWith('test abc');
});
})
update
angular.module('app',[]).factory('appfactory',function(){
var data;
var obj={};
obj.getValue=getValue;
obj.setValue=setValue;
return obj;
function getValue(){
return data;
}
function setValue(datavalue){
data=datavalue;
}
}).controller('cntrl',function($scope,appfactory){
appfactory.setValue('test abc');
$scope.message=appfactory.getValue()
})
I have changed your plunkr:
spy = jasmine.createSpy('spy');
fac.setValue = spy;
Edit
In Jasmine, mocks are referred to as spies. There are two ways to
create a spy in Jasmine: spyOn() can only be used when the method
already exists on the object, whereas jasmine.createSpy() will return
a brand new function.
Found the information here. The link has a lot more information about creating spies.
As said in the comments, you have absolutely no need for spies to test such a service. If you had to write the documentation for your service: you would say:
setValue() allows storing a value. This value can then be retrieved by calling getValue().
And that's what you should test:
describe('appfactory service',function(){
var appfactory;
beforeEach(module('app'));
beforeEach(inject(function(_appfactory_) {
appfactory = _appfactory_;
}));
it('should store a value and give it back',function() {
var value = 'foo';
appfactory.setValue(value);
expect(appfactory.getValue()).toBe(value);
});
});
Also, your service is not a factory. A factory is an object that is used to create things. Your service doesn't create anything. It is registered in the angular module using a factory function. But the service itself is not a factory.

How to make a REST API call in a controller Unit test?

I am trying to make a real call and Assign Scopes for testing
Using passThrough Method but Throwing Error
Code Follows:-
describe('Controller: MainCtrl', function () {
// load the controller's module
beforeEach(module('w00App'));
var scope, MainCtrl, $httpBackend;
// Initialize the controller and a mock scope
beforeEach(inject(function(_$httpBackend_, $rootScope, $controller) {
$httpBackend = _$httpBackend_;
$httpBackend.expectGET('http://api.some.com/testdata').passThrough();
scope = $rootScope.$new();
MainCtrl = $controller('MainCtrl', {
$scope: scope
});
})); it('should make a post to refresh the friends list and return matching users', function(){
var deferredResponse = $httpBackend.expectGET('http://api.some.com/testdata').passThrough();
console.log('response'+JSON.stringidy(deferredResponse));
$httpBackend.flush();
// expect(deferredResponse).toEqual(deferredResponse);
}); });
Error :- TypeError: 'undefined' is not a function (near '...
').passThrough();...') .....
How can i call and Assign Scopes Like in Real controller ? pls Help.. it make my life Easy .
When testing a real controller and inside the controller you make some REST calls to the backed, it is best to mock those response calls, intercept the calls via $httpBackend object.
jasmine.getJSONFixtures().fixturesPath = 'base/test/unit/authz/api_mock/';
$httpBackend.when('POST', CONFIG.get('MAIN_URL_FOR_REST_SERVICES') + 'actions/search').respond(function() {
return [200, window.getJSONFixture('actions.json')];
});
at least, this is how I proceed in testing the controllers.
if you really really want to call the backed use:
$http.get(YOUR_URL).success(function(data) {
--- your test ---
});
and do not forget do inject the http service in the beforeEach method:
beforeEach(inject(function(_$http_) {
$http = _$http_;
}));

AngularJs unit test - Check if "Init" function was called

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.

Spy on scope function that executes when an angular controller is initialized

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.

TypeError on a controller function`

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.

Resources