Jasmine testcase : How to resolve locale.ready() - angularjs

I am working on AngularJs 1.5 and just started off with Jasmine and Karma for testcases...
My controllers make use of
locale.ready('common').then(function () {
})
I am not able to mock 'locale'. Though its should have been straightforward as for other services.
I found something on the internet for this purpose, but the documentation does not have a working code:
'karma-json-preprocessor'
Can I get a sample code that demonstrates how to mock locale in this scenario?
EDIT: (29May'17)
Below is the sample code:
promise returns the object containing the localization keys & values:
angular.module('myApp', [])
.controller('mytestcontroller', ['$scope', 'locale',
function ($scope, locale) {
locale.ready('common').then(function (res) {
$scope.reportname = 'gauravreport';
});
}
]);
Below is a sample code I am trying:
var scope, controller;
beforeEach(inject(function($controller, $rootScope, locale) {
scope = $rootScope.$new();
controller = $controller('mytestcontroller', {
'$scope': scope,
'locale': locale
});
}));
it('check report name', function(done) {
var data = window.$json.$get('app/languages/en-US/home.lang.json');
locale.ready('myreport')
.then(function() {
expect(scope.reportname).toBe("gauravreport");
done();
})
.catch(done.fail);
});
Issue is when I run this test, it says reportname is not defined. It seems that its not able to resolve the locale service.

I have resolved this issue, using the approach stated here:
Testing Promises with Jasmine - Provide and Spy

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.

Unit testing decorators in Angular, decorating $log service

While it is fairly easy to unit test services/controllers in angular it seems very tricky to test decorators.
Here is a basic scenario and an approach I am trying but failing to get any results:
I defined a separate module (used in the main app), that is decorating $log service.
(function() {
'use strict';
angular
.module('SpecialLogger', []);
angular
.module('SpecialLogger')
.config(configureLogger);
configureLogger.$inject = ['$provide'];
function configureLogger($provide) {
$provide.decorator('$log', logDecorator);
logDecorator.$inject = ['$delegate'];
function logDecorator($delegate) {
var errorFn = $delegate.error;
$delegate.error = function(e) {
/*global UglyGlobalFunction: true*/
UglyGlobalFunction.notify(e);
errorFn.apply(null, arguments);
};
return $delegate;
}
}
}());
Now comes a testing time and I am having a really hard time getting it working. Here is what I have come up with so far:
(function() {
describe('SpecialLogger module', function() {
var loggerModule,
mockLog;
beforeEach(function() {
UglyGlobalFunction = jasmine.createSpyObj('UglyGlobalFunctionMock', ['notify']);
mockLog = jasmine.createSpyObj('mockLog', ['error']);
});
beforeEach(function() {
loggerModule = angular.module('SpecialLogger');
module(function($provide){
$provide.value('$log', mockLog);
});
});
it('should initialize the logger module', function() {
expect(loggerModule).toBeDefined();
});
it('should monkey patch native logger with additional UglyGlobalFunction call', function() {
mockLog.error('test error');
expect(mockLog.error).toHaveBeenCalledWith('test error');
expect(UglyGlobalFunction.notify).toHaveBeenCalledWith('test error');
});
});
}());
After debugging for a while I have noticed that SpecialLogger config section is not even fired.. Any suggestions on how to properly test this kind of scenario?
You're missing the module('SpecialLogger'); call in your beforeEach function.
You shouldn't need this part: loggerModule = angular.module('JGM.Logger');
Just include the module and inject the $log. Then check if your decorator function exists and behaves as expected.
After some digging I came up with a solution. I had to create and inject my own mocked $log instance and only then I was able to check weather or not calling error function also triggers call to the global function I was decorating $log with.
The details can be found on a blog post I wrote to explain this problem in detail. Plus I open sourced an anuglar module that makes use of this functionality available here

AngularJS "No pending request to flush" while unit testing a controller with a $resource

I am writing unit tests for a controller. This controller has a $resource service injected :
function controller($scope, Service) {
Service.get(function(result){
// do stuff with the result, not relevant here
}
}
The service is defined like this :
angular.module('so').factory('Service', ['$resource', service]);
function service($resource) {
return $resource('/url', null, {
get: { method: 'POST', params: {}, isArray: false}
});
}
My Jasmine unit test is the following :
describe("Controller", function(){
var $httpBackend;
beforeEach(function() {
module('so');
inject(function( _$httpBackend_) {
$httpBackend = _$httpBackend_;
});
});
it('should have done stuff irrelevant to the question', function() {
var $injector = angular.injector('so'),
$scope = $injector.get('$rootScope'),
$httpBackend
.whenPOST('/url')
.respond ([]);
// controller needs to be defined here and not in the beforeEach as there
// are more parameters passed to it, depending on the test
var controller = $injector.get('$controller')('controller', { "$scope": $scope });
$httpBackend.flush();
// then here the actual test resolution, also irrelevant
});
});
I get an error when running the test :
Error: No pending request to flush ! in file:///path/to/angular-mock.js (line 1453)
I added a console.log() in the callback from Service.get() and indeed, it is not called (everything outside of the callback is of course called). Also tried to add a scope digest if not phased after controller creation in the unit test, as I saw suggested in an other question, with no luck.
I know that I can mock that in some other ways, but using $httpBackend seems the perfect solution for the test : mocking the webserver and the data received.
I'm using AngularJS 1.2.16 (can't upgrade to 1.3.*, IE 8 compatibility required). I first used 1.2.13 and updated to check if it would solve the issue, without any luck.
That was an injection issue that was solved by changing the test from
it('should have done stuff irrelevant to the question', function() {
var $injector = angular.injector('so'),
$scope = $injector.get('$rootScope'),
$httpBackend
.whenPOST('/url')
.respond ([]);
// controller needs to be defined here and not in the beforeEach as there
// are more parameters passed to it, depending on the test
var controller = $injector.get('$controller')('controller', { "$scope": $scope });
$httpBackend.flush();
// then here the actual test resolution, also irrelevant
});
To:
it('should have done stuff irrelevant to the question', inject(function(Service) {
// edited lines because they did not change
var controller = $injector.get('$controller')('controller', { "$scope": $scope, "Service": Service });
// edited lines because they did not change
}));
So basicaly, adding the inject() in the test function and passing the service to the controller "manually".
I found the issue, that's great, but I don't really understand why it doesn't work. Also, I tried this right after finding the solution :
it('should have done stuff irrelevant to the question', inject(function() {
// edited lines because they did not change
var Service = $injector.get('Service'),
var controller = $injector.get('$controller')('controller', { "$scope": $scope, "Service": Service });
// edited lines because they did not change
}));
but this fail again, with the same "no pending request" error. I'm guessing that's some sort of racing issue, where my service can't get the proper $httpBackend to be injected when it's created afterwards, but I don't really understand why this occurs. If anybody can enlighten me... I'll be grateful.

Angularjs and Jasmine: Testing a controller with a service making ajax call

I am very new to testing javascript. My application is using angularjs. I am using jasmine as a testing framework.
Here is the controller I am testing:
angular.module('logonController', ["ngval", "accountFactory"])
.controller("logonController", function logOnController(accountFactory, $scope, $window) {
$scope.hasServerError = false;
$scope.Logon = function () {
accountFactory.Logon($scope.data.LogOnModel)
.then(function (data) {
$window.location.href = "/";
},
function (data) {
$scope.hasServerError = true;
});
}
})
where accountFactory.Logon is making a Post request to the server.
What I want to test is when calling accountFactory.Logon:
On success - window.location.href is called
On error $scope.hasServerError is set to true
So far I have managed to do this:
"use strict";
describe("Logon Controller", function () {
var $scope, $location, $rootScope, $httpBackend, $controller, $window, createController;
beforeEach(function () {
module("logonController");
});
beforeEach(inject(function ($injector) {
$rootScope = $injector.get("$rootScope");
$scope = $rootScope.$new();
$location = $injector.get("$location");
$httpBackend = $injector.get("$httpBackend");
$controller = $injector.get("$controller");
$window = $injector.get("$window");
}));
beforeEach(function () {
createController = function () {
return $controller("logonController", {
"$scope": $scope,
});
};
$scope.data = {
LogOnModel: { username: "user", password: "pass" }
};
$window = { location: { href: jasmine.createSpy() } };
});
it("should redirect on successfull login", function () {
var controller = createController();
$httpBackend.whenPOST("/Account/Logon").respond(function (method, url, data, headers) {
return [200, {}, {}];
});
$scope.Logon();
$httpBackend.flush();
expect($window.location.href).toHaveBeenCalled();
});
});
My idea is to create a spy on $window.location.href and only check if it is called. But I am getting
Expected spy unknown to have been called.
As I said I am very new to testing javascript, so any help will be appreciated.
Sten Muchow's Answer is wrong in several aspects:
you can't specify a compound property name ("location.href") as 2nd parameter to spyOn. You have to give a real property name.
And even if you would do the spyOn correctly, andCallThrough() would still raise an exception, as $window.location.href is not a function which could be called through.
But he is still right in saying that you should not intermingle your controller test with the service test.
To answer the question:
The reason, that your expectation is not met (that even the spy still exists*) is, that you're doing the $window.location.href assignment inside a promise's then() function. That means, it will be executed asynchronously, namely AFTER your expect() call. To go around this, you would need to make your test work asynchronously (for how to do this I would like to advise you to the Jasmine documentation: http://jasmine.github.io/2.0/introduction.html).
* In accountFactory.Logon, by doing $window.location.href = (i.e. assignment) you will effectively overwrite your spy.
Even better solution:
Instead of manipulating $window.location.href, you should use $location.url().
$location is an Angular core service. You will benefit from the integration within the Angular application lifecycle (i.e. watchers will be automatically processed when the url changes) + it is seamlessly integrated with existing HTML5 APIs like History API: https://docs.angularjs.org/guide/$location
Then, you can spy on $location.url() as you would have spied on $window.location.href (if it had been a function).
You need to create a spy:
spyOn($window, 'location.href').andCallThrough();
But on a bigger note though, you shouldnt be testing the functionality of your service in the controller test.

angularjs how to define a new property on $scope without a view

I am trying BDD with Jasmine and angularJS app. My requirement is to create a select element in my view which gets it's data from a factory. So in true spirit of BDD, I am writing my factory and controller first before I start writing my view.
My test for factory:
describe('getTypeOfUnit', function(){
it('should return typeofunits', inject(function(getTypeOfUnit){
expect(getTypeOfUnit).not.toBeNull();
expect(getTypeOfUnit instanceof Array).toBeTruthy();
expect(getTypeOfUnit.length).toBeGreaterThan(0);
})) ;
});
So I am testing that my data is not null, is an array and contains at least one item. It fails since there is no factory.
This is the factory to make the tests pass:
angular.module('myApp.services', [])
.factory('getTypeOfUnit', function(){
var factory = ['Research Lab', 'Acedamic Unit', 'Misc'];
return factory;
});
Now onto the controller. Here is the empty controller:
angular.module('myApp.controllers', [])
.controller('describeUnitController',[function($scope){
console.log('exiting describeUnit');
}]);
And tests for controller:
describe('controllers', function(){
var describeScope;
beforeEach(function(){
module('myApp.controllers');
inject(function($rootScope, $controller){
console.log('injecting contoller and rootscope in beforeEach');
describeScope = $rootScope.$new();
var describerController = $controller('describeUnitController', {$scope: describeScope});
});
}) ;
it('should create non empty "typeOfUnitsModel"', function() {
expect(describeScope["typeOfUnits"]).toBeDefined();
var typeOfUnits = describeScope.typeOfUnits;
expect(typeOfUnits instanceof Array).toBeTruthy();
expect(typeOfUnits.length).toBeGreaterThan(0);
});
});
So I am testing that my controller returns a non empty array. Same as the service. These tests fail. So next step is to define a property on the scope object in the controller:
.controller('describeUnitController',[function($scope){
$scope.typeOfUnits = [];
console.log('exiting describeUnit');
}]);
Now I get an error:
TypeError: Cannot set property 'typeOfUnits' of undefined
Why does the controller not know about the scope? I thought DI would automatically make it available?
Thank you in advance. Also please comment on my tests.
Found two mistakes with my code:
The controller does not know about the $scope. Not sure why. So I can do one of the following:
.controller('describeUnitController',['$scope',function($scope){
$scope.typeOfUnits = [];
console.log('exiting describeUnit');
}]);
OR
.controller('describeUnitController',function describeUnitController($scope){
$scope.typeOfUnits = [];
console.log('exiting describeUnit');
});
I am not sure why it is this way. But lesson learned.
I then tried to use the service as follows:
.controller('describeUnitController',function describeUnitController($scope, GetTypeOfUnit){
$scope.typeOfUnits = getTypeOfUnit;
});
This gave me the famous Error: unknown provider for GetTypeOfUnit
Apparantly, I have to add the services module to the factory module in order to use the service and make the tests pass. But my app.js has them all defined:
angular.module('myApp', ['myApp.filters', 'myApp.services', 'myApp.directives', 'myApp.controllers','ui.bootstrap','$strap.directives'])
But since I am testing, I have to load the services module in the controllers module as well. If the app was loading (like in the browser), I would not have to do this.
Am I understanding this correctly? Thank you all.

Resources