Jasmine: How to test a controller function calling other javascript function - angularjs

Here is the sample code:
App.controller('Crtl', ["$scope", function ($scope) {
$scope.FetchDetail = function () {
var accNum = this.customer.accNo;
GetAccountDetails(accNum);
}; }]);
I am new to Jasmine and right now I am writing unit tests for my angular code. Here I created FetchDetail function which then calls javascript function GetAccountDetails(accNum).
How can I test this sample using Jasmine.

It depends if you need to stub it (i.e. capture and change its behaviour) or if it is enough to spy on it (watch it). Either way, you would be better off injecting it, so you can control it.
I have been using sinon http://sinonjs.org quite happily.
If you want to spy on it, i.e. watch its behaviour without changing it, then before calling it, you would do
var spy = sinon.spy(GetAccountDetails);
Then you can check if it was called, or what arguments were, etc.
spy.calledOnce === true; // if it was called once
spy.called === true; // if it was called at all
spy.firstCall.args[0]; // first argument to first call
// etc. - check out the docs
If you need to stub it, use the stub functionality, where you can then define what the behaviour should be. You get all of the spying features, but also control what the response is. However, it is hard to spy on a global, as opposed to a method of an existing object.

Related

Make jasmine test wait until dependent method call's promise is resolved

I have to test a void method which has a dependent method call that returns a promise and I can't mock that call as it is made on a local object created inside the tested method.
Is there a way to make jasmine expect calls to wait until the promise is resolved? I tried using $rootScope.$digest() but it is not ensuring that the dependent call's promise is resolved.
EDIT: Adding sample code
module.service('serviceToBeTested', ['$rootScope', 'someOtherService',
function($rootScope, someOtherService) {
var thirdPartyLib;
function fnToBeTested() {
//some validations and filtering on rootScope variable to build input for processing
thirdPartyLib = new ThirdPartyLib(); //this is not an angular service
var anotherFunction = function() {
//some hook functions that will be triggered by the third party library
}
// anotherFunction is set into thirdPartyLib so that hook functions will be triggered
thirdPartyLib.start().then(funtion() {
thirdPartyLib.someThing.load(); //this load will trigger one hook function
}
}
}]);
What I need to verify is that, upon invoking fnToBeTested(), a particular logic inside a hook function is executed (for that the control has to go inside the then part of thirdPartyLib.start()).
Actually this gets executed but only after the expect statements in the spec are executed.
And my spec file is almost like this:
it('should do this and this', function() {
// some initialization
serviceToBeTested.fnToBeTested();
$rootScope.$digest();
//expect statements
});
EDIT 2: Adding details on trial made as Andrew suggested below and adding clarity on how instance is created
ThirtPartyLib is instantiated inside main source as:
var theLib = require('theLib');
...............................
thirdPartyLib = new theLib.ThirdPartyLib();
And in spec, I created a var just like this and spied prototype as below:
var theLib = require('theLib');
................................
spyOn(theLib.ThirtPartyLib.prototype, 'start').and.callFake(.....);
But the fake function is not reached. When I check theLib.ThirtPartyLib.prototype in spec during debug, it lists the SpyStrategy while checking theLib.ThirtPartyLib.prototype in main source, it doesn't list that.
You should be able to test this with some clever use of mocking. In your beforeEach block, you can do something like this:
let promise; // declare this outside of your beforeEach so you have access to it in the specs
promise = $q.resolve(); // assign promise to
spyOn(ThirdPartyLib.prototype, 'start').and.returnValue(promise);
And then in your test, you now have access to the promise returned by start.

Unit Testing Angular Function using Karma Jasmine

I have a function like this inside a controller
$scope.closeMenu = function () {
$('#menu').hide();
};
If a functions returns a value I am able to test using
expect($scope.value).toEqual();
How do we test the above function using jasmine. Please help thanks
If a functions returns a value I am able to test using expect($scope.value).toEqual(); How do we test the above function using jasmine
You should rewrite your function, so as it only set a variable of the model. Then it will be testable using the way you already know in Jasmine. One of the point of angular is that you don't manipulate the DOM from the controller. Providing you follow these guidelines, controllers are far easier to test.
Moreover, in your case rewriting is very straightforward:
$scope.closeMenu = function () {
$scope.isOpen = false;
};
Template:
... id="menu" ng-show="isOpen" ...
In case you still need to test some characteristics of a DOM element, for example it's visibility, jasmine-jquery might be useful for you. Example:
expect($('#menu')).toBeHidden()

Updating 'this' Context Property Inside of a $Promise In An Angular JS Service

I have a function being used in my service that is defined as:
var getData = function() {
return anotherService.getData().$promise;
};
and a this property that I manipulate throughout the service.
this.someProperty = 'a string';
I call the above function inside the return section of my service:
return{
updateProperty: function(){
getData().then(function(data){
this.someProperty = data;
});
}
}
In the above, I get an this is undefined related error in my browser console. I assume this is because the resolved $promise is an AJAX call and this is used out of context. What's the best way to manipulate a this property using the returned data from an AJAX call in this instance?
if you're manipulating this throughout your service, assign it to a variable like var self = this. The problem is that this is the context of a function and can be changed by other code using fn.call(context) or fn.apply(context, args). So it's liable to be different inside of the scope of any given function.
So just assign it to some variable and use it:
var self = this;
return {
updateProperty: function(){
getData().then(function(data){
self.someProperty = data;
});
}
};
The simplest way would be to use the bind function. This function sets the 'this' context for the function call. So, in your case you'd have to use it twice so that the proper 'this' populates in.
return{
updateProperty: function(){
getData().then((function(data){
this.someProperty = data;
}).bind(this));
}
}
This comes to ensure that the handler you passed to the promise is executed with the original 'this' (passed to updateProperty). Now, to pass the correct 'this' value to the updateProperty function, you should, in your controller do:
(myService.updateProperty.bind(this))();
There are numerous versions of binding, including binding the entire service. Also, have a look at lodash for function extensions.
I prepared a small pen to demonstrate this. It covers what I listed above, plus another important thing to note. When you use setTimeout, the handler is invoked with in the global context (in this case, 'window'), this is why I added a third bind, to make sure 'this' is relevant inside the timeout handler. I also added various count increment calls to demonstrate that 'this' is the same value along the way.
If this is a repeating scenario, you might want to pass either the target object (and then use the handler just to know it was updated), or a handler (which also needs binding). I added examples for these scenarios as well.
One last word, call, apply and bind are key to javascript and worth learning. Put some time into it and work your way out of context hell.

Mocking Angulars ng-init function in a Jasmine test

I would like to inject this function into the ng-init directive when I instantiate a controller in an angular test, in the html I would set it up like this:
<div ng-controller="my-ctrl" ng-init="initialize({updateUrl: 'list_json'})
In my test I could just write a function like this which would stick the correct stuff in the scope but I think it is messy. Also I am worried about the function currently hard-wired in the html messing it up in my midway test
function initController($scope){
$scope['updateUrl'] = 'list_json'
}
I don't think that ng-init creates any functions by itself, so I think it's just a wrong way of using it (judged by your snippets).
What I usually do is create the init function myself and then test it without a problem in the controller's unit test.
<div ng-controller="my-ctrl" ng-init="init({ updateUrl : 'list_json' })">...</div>
function MyController($scope){
$scope.init = function(config) {
$scope.updateUrl = config.updateUrl;
}
}
Then in Jasmine you can without a problem write a test which would call the init() function (boilerplate skipped):
// Given
var config = { updateUrl : 'list_json' };
// When
myController.init(config);
// Then
// ...
Important comment about Unit Testing
If you think that you should be testing if your HTML markup is calling init() with given parameters, then I think you are wrong. There is simply nothing to test. There is no logic there at all! Your tests should be verifying that components work how they should work, if programmer will make a mistake with passing wrong parameters from HTML to Angular, then you shouldn't be bothered with covering such mistakes in unit tests. You can't force people not to make mistakes :-)

How to stub out a controller method called during object construction

I have an AngularJs controller that calls its own refresh() method while it is being constructed. The method in question accesses some template elements that are not present during unit testing.
function ListController($scope) {
/// ...
$scope.refresh = function() {
var tabId = angular.element('#id li.active a').attr('href');
//etc
}
//Initialise
$scope.refresh();
}
The refresh method causes unit tests to fail while the controller is being constructed. As the work it does is irrelevant to the tests, I want to override the method with a stub and simply test that it has been called.
Jasmine's Spy functionality looks like the way to go, but I can't find a way of setting one up for an object before it is constructed. How would I do this?
You should move this to a directive's link function. A link function is basically the result of a compile so Then you will know for sure that your element is compiled and ready, and that will make your "refresh" function unnecessary. In general, you should never access DOM via jqLite or jQuery from the controller. Also, the link function provides direct access to element, scope, and attributes (even href) which is nice.

Resources