Issue with jasmine spies call through - javascript

I am having trouble calling through to the actual implementation and I am getting this error:
TypeError: undefined is not an object (evaluating 'GitUser.GetGitUser('test').then') ...
Here are my codes:
app.controller('HomeController', ['$scope', 'GitUser', function ($scope, GitUser) {
$scope.name = "user";
GitUser.GetGitUser('test').then(function (data) {
console.log(data);
if (data) {
$scope.name = data;
}
});
}]);
app.factory('GitUser', function ($http) {
return {
GetGitUser: function (username) {
return $http.get('https://api.github.com/users/' + username)
.then(function success(response) {
return response.data.login;
});
}
};
});
Here is my unit test:
describe('HomeController Unit Test', function () {
var $controllerConstructor, scope;
beforeEach(module("AngularApp"));
beforeEach(inject(function ($controller, $rootScope) {
$controllerConstructor = $controller;
scope = $rootScope.$new();
}));
it('should test if scope.name is test', function () {
// Act
GitUser = {
GetGitUser: function () { }
};
spyOn(GitUser, "GetGitUser").and.callThrough();
GitUser.GetGitUser();
$controllerConstructor('HomeController', {
'$scope': scope,
'GitUser': GitUser
})
// Assert
expect(GitUser.GetGitUser).toHaveBeenCalled();
expect(scope.name).toBe('test');
});
});

The problem is a bit more complex than just a missing inject ...
Here's an adjusted test:
https://plnkr.co/edit/ZMr0J4jmLPtDXKpRvGBm?p=preview
There are a few problems:
1) you are testing a function that returns a promise - so you need to also mock it that way (by using return $q.when(..) for example).
2) you are trying to test code that happens when your controller is created - the
GitUser.GetGitUser('test').then(function (data) {
console.log(data);
if (data) {
$scope.name = data;
}
});
should be wrapped in a function instead:
function init() {
GitUser.GetGitUser('test').then(function (data) {
console.log(data);
if (data) {
$scope.name = data;
}
});
}
and then make that available on your scope:
scope.init= init;
Then in your test call the function and verify your assertions. If you don't wrap it in a function it won't be testable.
Also - the mocking and the callThrough thing ... as you are testing the controller (and not the service) you can use callFake instead - the callFake function can return a Promise with a value (the one that you want to verify later) - then you can ensure that the controller part of the puzzle works.
var name = 'test';
// instead of trying to mock GitUser you can just callFake and be sure to return a promise
spyOn(GitUser, "GetGitUser").and.callFake(function() {
return $q.when(name);
});
I hope this all makes sense - the plunker should make things clear - I will add some more comments there.

I think you just miss something here
beforeEach(inject(function ($controller, $rootScope, _GitUser) {
$controllerConstructor = $controller;
scope = $rootScope.$new();
GitUser = _GitUser;
}));

Related

Unit test inject dependency like controller for service in angularJS

Lets see, we have this according to:https://docs.angularjs.org/guide/unit-testing
describe('PasswordController', function() {
beforeEach(module('app'));
var $controller;
beforeEach(inject(function(_$controller_){
// The injector unwraps the underscores (_) from around the parameter names when matching
$controller = _$controller_;
}));
describe('$scope.grade', function() {
it('sets the strength to "strong" if the password length is >8 chars', function() {
var $scope = {};
var controller = $controller('PasswordController', { $scope: $scope });
$scope.password = 'longerthaneightchars';
$scope.grade();
expect($scope.strength).toEqual('strong');
});
});
});
now i am making service and factory, is there any equivalent to ____$controller____ for service and factory? so i can inject it with something else like:
var controller = $controller('PasswordController', { $scope: $scope });
and change the inner functions of the dependency so i can test it, or is there any better approach?
Edit: to make question more clear
here is the example of the question:
i have this:
var app = angular.module("app").service("MyService",["$scope","$http",function($scope,$http){
this.myFunction = function(){
$http.get("/myApi/1");
}
}]);
how do i use the equivalent of
var controller = $controller('PasswordController', { $scope: $scope });
so i can inject $scope and $http with something else to myService?
You can't inject dependencies to factories or services on the go, but you can mock the dependencies with your custom objects and have angular substitute them automatically. You can use $provide for that. Here is an example:
angular.module('app').service('some', function(dependencyService) {
});
When testing:
beforeEach(module(function($provide) {
$provide.value('dependencyService', {
});
}));
After doing a workarround, i found out from https://www.sitepoint.com/unit-testing-angularjs-services-controllers-providers/ about the service. i tested out the tutorial here and here is the test script:
(function () {
angular.module('services', [])
.service('sampleSvc', ['$window', 'modalSvc', function ($window, modalSvc) {
this.showDialog = function (message, title) {
if (title) {
modalSvc.showModalDialog({
title: title,
message: message
});
} else {
$window.alert(message);
}
};
}]);
describe("Testing service", function () {
var mockWindow, mockModalSvc, sampleSvcObj;
beforeEach(module(function ($provide) {
$provide.service('$window', function () {
this.alert = jasmine.createSpy('alert');
});
$provide.service('modalSvc', function () {
this.showModalDialog = jasmine.createSpy('showModalDialog');
});
}, 'services'));
beforeEach(inject(function ($window, modalSvc, sampleSvc) {
mockWindow = $window;
mockModalSvc = modalSvc;
sampleSvcObj = sampleSvc;
}));
it('should show alert when title is not passed into showDialog', function () {
var message = "Some message";
sampleSvcObj.showDialog(message);
expect(mockWindow.alert).toHaveBeenCalledWith(message);
expect(mockModalSvc.showModalDialog).not.toHaveBeenCalled();
});
it('should show modal when title is passed into showDialog', function () {
var message = "Some message";
var title = "Some title";
sampleSvcObj.showDialog(message, title);
expect(mockModalSvc.showModalDialog).toHaveBeenCalledWith({
message: message,
title: title
});
expect(mockWindow.alert).not.toHaveBeenCalled();
});
});
})();
and i try my own test script:
(function () {
describe("Testing service", function () {
var mockHttp, mockCookies, mockApi;
beforeEach(function () {
module(function ($provide) {
$provide.service('$http', function () {
this.defaults = {
headers: {
common: {
}
}
};
});
$provide.service('$cookies', function () {
});
});
module('timesheet');
});
beforeEach(inject(function ($http, $cookies, APIService) {
mockHttp = $http;
mockCookies = $cookies;
mockApi = APIService;
}));
it('Test Service', function () {
});
});
})();
apparently in somewhere in my code, there is an app.run which inside do the
$http.defaults.headers.common.Authorization = 'Bearer ' + $cookies.get('sessionToken');
and causes the error the moment i inject the $http with something else because headers not defined, i thought it was from my own test script because they are using same name, but apparently this is the one causing problem.
So, actually the moment we load in testing mode, the angularjs still do the whole running of application, in which i forgot about this one.

Testing a Function in angular

I am trying to write a test to verify that a function is called when the page is loaded. I am trying to spyon it but its saying that it does not exist.
Could someone please give me some help on how to properly spy on it?
Here is the angular code:
(function () {
angular
.module('uotc')
.controller('reportGroupArchiveCtrl', ReportGroupArchiveCtrl);
ReportGroupArchiveCtrl.$inject = ['$scope', '$http', '$stateParams', '$q'];
function ReportGroupArchiveCtrl($scope, $http, $stateParams, $q) {
$scope.reportType = $stateParams.reportType;
$scope.templateTypeId = $stateParams.templateTypeId;
$scope.group = $stateParams.group;
//Initialize the page
activate();
function activate() {
//Call the API to get the generated reports for this group
$http.get('api/url/params....)
.then(getGeneratedReportsComplete)
.catch(apiCallFailed);
}
function getGeneratedReportsComplete(data, status, headers, config) {
//do stuff
};
function apiCallFailed(e) {
return null;
}
}
})();
Here is the test:
describe('Controller', function () {
var stateparams, controller, scope;
beforeEach(angular.mock.module('uotc'));
beforeEach(angular.mock.inject(function (_$controller_, _$rootScope_) {
stateparams = { reportType: 'Vendor', templateTypeId: 2, group: 'VENDOR_1' };
scope = _$rootScope_.$new();
controller = _$controller_('reportGroupArchiveCtrl',{
$scope: scope,
$stateParams:stateparams
});
spyOn(controller, 'getGeneratedReportsComplete')
}));
describe('is loaded', ()=> {
beforeEach(inject(function($injector) {
$httpBackend = $injector.get('$httpBackend');
$httpBackend
.when('GET', 'api/url/params)
.respond(200, '123')
$httpBackend.flush();
}));
it('calls the generated reports API', ()=> {
expect(controller.getGeneratedReportsComplete()).toHaveBeenCalled();
})
})
});
This is wrong:
expect(controller.getGeneratedReportsComplete()).toHaveBeenCalled();
This is better:
expect(controller.getGeneratedReportsComplete).toHaveBeenCalled();
You pass the function itself to expect; you shouldn't call it.
The other problem is that getGeneratedReportsComplete is declared as a local variable inside the constructor, so it's not accessible outside that, and you cannot spy on it. Instead, define it on the this object:
this.getGeneratedReportsComplete = (data, status, headers, config) => {
//do stuff
};
Of course you need to call it as this.getGeneratedReportsComplete as well.

How to test that my service returns data with jasmine and angularjs

I've begun using jasmine to test my controllers in angularjs however after reading some tutorials I am a bit stuck.
I have this simple angularjs controller called jasmineController
(function () {
"use strict";
var myAppModule = angular.module('myApp');
myAppModule.controller('jasmineController', ['$scope', 'genericService',
function ($scope, genericService) {
$scope.name = 'Superhero';
$scope.counter = 0;
$scope.$watch('name', function (newValue, oldValue) {
$scope.counter = $scope.counter + 1;
});
$scope.testPromise = function() {
return genericService.getAll("dashboard", "currentnews", null, null, null);
}
$scope.getNewsItems = function () {
genericService.getAll("dashboard", "currentnews", null, null, null).then(function (data) {
$scope.name = 'Superhero';
$scope.newsItems = data;
});
}
}
]);
})();
In my jasmine test I wanted to call getNewsItems and check that it can call genericService.getAll and that $scope.newsItems is assigned some data. I understand that I would be mocking out the service and I won't actually call it.
Here is my spec
describe("test", function () {
// Declare some variables required for my test
var controller, scope, genericService;
// load in module
beforeEach(module("myApp"));
beforeEach(inject(function ($rootScope, $controller, _genericService_) {
genericService = _genericService_;
// assign new scope to variable
scope = $rootScope.$new();
controller = $controller('jasmineController', {
'$scope': scope
});
}));
it('sets the name', function () {
expect(scope.name).toBe('Superhero');
});
it('should assign data to scope', function() {
//var fakeHttpPromise = {success: function () { }};
scope.getNewsItems();
spyOn(genericService, 'getAll');
expect(genericService.getAll).toHaveBeenCalledWith('dashboard', 'currentnews');
});
});
I've got a spyon for genericService.getall() but apart from that I am a bit stuck with checking that my scope variable is assigned a value.
I also get this stack trace:
Error: Expected spy getAll to have been called with [ 'dashboard', 'currentnews' ] but it was never called.
at stack (file:///C:/Users/nickgowdy/Local%20Settings/Application%20Data/Microsoft/VisualStudio/12.0/Extensions/4sg2jkkc.gb4/TestFiles/jasmine/v2/jasmine.js:1441:11)
at buildExpectationResult (file:///C:/Users/nickgowdy/Local%20Settings/Application%20Data/Microsoft/VisualStudio/12.0/Extensions/4sg2jkkc.gb4/TestFiles/jasmine/v2/jasmine.js:1408:5)
at expectationResultFactory (file:///C:/Users/nickgowdy/Local%20Settings/Application%20Data/Microsoft/VisualStudio/12.0/Extensions/4sg2jkkc.gb4/TestFiles/jasmine/v2/jasmine.js:533:11)
at Spec.prototype.addExpectationResult (file:///C:/Users/nickgowdy/Local%20Settings/Application%20Data/Microsoft/VisualStudio/12.0/Extensions/4sg2jkkc.gb4/TestFiles/jasmine/v2/jasmine.js:293:5)
at addExpectationResult (file:///C:/Users/nickgowdy/Local%20Settings/Application%20Data/Microsoft/VisualStudio/12.0/Extensions/4sg2jkkc.gb4/TestFiles/jasmine/v2/jasmine.js:477:9)
at Anonymous function (file:///C:/Users/nickgowdy/Local%20Settings/Application%20Data/Microsoft/VisualStudio/12.0/Extensions/4sg2jkkc.gb4/TestFiles/jasmine/v2/jasmine.js:1365:7)
at Anonymous function (file:///C:/Projects/2013/AMT2015/AMT2015.WebAPP/Scripts/tests/controllers/dashboardControllerSpec.js:49:9)
at attemptSync (file:///C:/Users/nickgowdy/Local%20Settings/Application%20Data/Microsoft/VisualStudio/12.0/Extensions/4sg2jkkc.gb4/TestFiles/jasmine/v2/jasmine.js:1759:9)
at QueueRunner.prototype.run (file:///C:/Users/nickgowdy/Local%20Settings/Application%20Data/Microsoft/VisualStudio/12.0/Extensions/4sg2jkkc.gb4/TestFiles/jasmine/v2/jasmine.js:1747:9)
at QueueRunner.prototype.execute (file:///C:/Users/nickgowdy/Local%20Settings/Application%20Data/Microsoft/VisualStudio/12.0/Extensions/4sg2jkkc.gb4/TestFiles/jasmine/v2/jasmine.js:1733:5)
You need to put the spy first before calling the test function. And you are you actually passing more parameters to the service function. So you need to test with the exact parameter list.
it('should assign data to scope', function() {
//var fakeHttpPromise = {success: function () { }};
spyOn(genericService, 'getAll');
scope.getNewsItems();
expect(genericService.getAll).toHaveBeenCalledWith('dashboard', 'currentnews',null,null,null);
});
I ended up doing this:
describe("test", function () {
// Declare some variables required for my test
var controller, scope, genericService;
// load in module
beforeEach(module("myApp"));
beforeEach(inject(function ($rootScope, $controller, _$q_, _genericService_) {
genericService = _genericService_;
var deferred = _$q_.defer();
deferred.resolve('resolveData');
spyOn(genericService, 'getAll').and.returnValue(deferred.promise);
scope = $rootScope.$new();
controller = $controller('jasmineController', {
'$scope': scope
});
}));
it('sets the name', function () {
expect(scope.name).toBe('Superhero');
});
it('should assign data to scope', function() {
//spyOn(genericService, 'getAll').and.callFake(function() {
//});
scope.getNewsItems();
scope.$apply();
expect(scope.newsItems).toBe('resolveData');
//expect(genericService.getAll).toHaveBeenCalledWith('dashboard', 'currentnews', null, null, null);
});
});
Because my test is more than just calling a service but handling a promise as well I had to inject $q. Then with spy on I say to call service and method and the return value is the deferred promise.
Finally I can look at the scope variable to see if anything is assigned with this line:
expect(scope.newsItems).toBe('resolveData');
Thanks to everyone that helped.

How to spy on anonymous function using Jasmine

I'm using Jasmine to test my angular application and want to spy on an anonymous function.
Using angular-notify service https://github.com/cgross/angular-notify, I want to know whether notify function have been called or not.
Here is my controller:
angular.module('module').controller('MyCtrl', function($scope, MyService, notify) {
$scope.isValid = function(obj) {
if (!MyService.isNameValid(obj.name)) {
notify({ message:'Name not valid', classes: ['alert'] });
return false;
}
}
});
And here is my test:
'use strict';
describe('Test MyCtrl', function () {
var scope, $location, createController, controller, notify;
beforeEach(module('module'));
beforeEach(inject(function ($rootScope, $controller, _$location_, _notify_) {
$location = _$location_;
scope = $rootScope.$new();
notify = _notify_;
notify = jasmine.createSpy('spy').andReturn('test');
createController = function() {
return $controller('MyCtrl', {
'$scope': scope
});
};
}));
it('should call notify', function() {
spyOn(notify);
controller = createController();
scope.isValid('name');
expect(notify).toHaveBeenCalled();
});
});
An obviously return :
Error: No method name supplied on 'spyOn(notify)'
Because it should be something like spyOn(notify, 'method'), but as it's an anonymous function, it doesn't have any method.
Thanks for your help.
Daniel Smink's answer is correct, but note that the syntax has changed for Jasmine 2.0.
notify = jasmine.createSpy().and.callFake(function() {
return false;
});
I also found it useful to just directly return a response if you only need a simple implementation
notify = jasmine.createSpy().and.returnValue(false);
You could chain your spy with andCallFake see:
http://jasmine.github.io/1.3/introduction.html#section-Spies:_andCallFake
//create a spy and define it to change notify
notify = jasmine.createSpy().andCallFake(function() {
return false;
});
it('should be a function', function() {
expect(typeof notify).toBe('function');
});
controller = createController();
scope.isValid('name');
expect(notify).toHaveBeenCalled();

Testing an Angular Promise with Jasmine

The following test keeps failing and I can't figure out why? I am trying to figure out how to test defereds/promises with Jasmine.
Error
Expected undefined to be 'Resolved Data'.
Test
describe('Queued Repository', function () {
var ctrl,
rootScope,
scope,
service;
beforeEach(function () {
module('testApp');
inject(function ($rootScope, $controller, TestSrvc) {
rootScope = $rootScope;
scope = $rootScope.$new();
service = TestSrvc;
});
});
afterEach(inject(function ($rootScope) {
$rootScope.$apply();
}));
it('test something', function () {
expect(service.calculate(1, 5)).toBe(6);
});
it('resolves promises', function () {
var result;
service.getPromise().then(function (data) {
result = data;
});
rootScope.$apply();
expect(result).toBe('Resolved Data');
});
});
Service
var app = angular.module('testApp', []);
app.service('TestSrvc', ['$q', '$timeout', '$http', function ($q, $timeout, $http) {
return {
getPromise: function () {
var d = $q.defer();
$timeout(function () {
d.resolve('Defered Result');
}, 5000);
return d.promise;
},
getSomething: function () {
return "Test";
},
calculate: function (x, y) {
return x + y;
}
}
}]);
Try calling $timeout.flush() before expect(result).toBe('Resolved Data');.
In your example, you will need to call both $timeout.flush() AND $rootScope.$apply().
Explanation: $timeout.flush() will force your $timeout in the service to run immediately. Your service will then call 'resolve' - but the promise.then() will not be called until the subsequent digest cycle; therefore you will need to call $rootScope.$apply() to propagate any 'resolves' and 'watches' - which will occur synchronously.
NOTE: In Jasmine, ensure that your promise.then() function appears BEFORE your call to $rootScope.$apply otherwise it will not fire the promise.then() function. (I haven't figured out why this is the case in Jasmine.)

Categories