I would like to test my angular service I would like to test it with real data - a.k.a (Integration Test). I'm using Jasmine and Karma.
Here is my test:
describe('Trending Data Service', function () {
var value = 0, originalTimeout = 0;
var service, Enums, $httpBackend;
// initialize module
beforeEach(module('waterfall'));
// initialize services
beforeEach(inject(function ($injector) {
jasmine.DEFAULT_TIMEOUT_INTERVAL = 10000;
$httpBackend = $injector.get('$httpBackend');
service = $injector.get("trendingService");
Enums = $injector.get("Enums");
spyOn(service, 'fetch').and.callThrough();
}));
it('check if dependencies are defined', function () {
expect(service).toBeDefined();
expect(Enums).toBeDefined();
expect(service.categories).toBeDefined();
expect(service.fetch).toBeDefined();
});
it('categories array should be defined within the service', function () {
expect(service.categories.length).toEqual(9);
expect(service.categories).toEqual(jasmine.any(Array));
});
// this test is alway fails...
it('fetch method should return initial result', function (done) {
var promise = service.fetch(Enums.socials.viewAll, false);
promise.then(function (result) {
done();
}, function() {
expect(1).toBe(2);
done.fail('Error occured');
});
});
}
This is the error:
Error: Timeout - Async callback was not invoked within timeout specified by jasmine.DEFAULT_TIMEOUT_INTERVAL.
I tried a wide range of solutions and I haven't got any success with this.
EDIT: 29/April/2016
var trendingService = function ($q, $http) {
var deferred = $q.defer();
var $this = this;
this.fetch = function (id) {
$http.get(url).then(function (result) {
deferred.resolve(result);
}).catch(function(err) {
deferred.reject(err);
});
return deferred.promise;
}
return {
fetch: $this.fetch;
};
};
var Enums = {
Roles: {
Admin: 1,
User: 2,
NotRegistered: 0
}
};
angular.module('').const('Enums', Enums);
Karma isn't meant for integration testing. Your call to module('waterfall') is actually a reference to angular.mock.module which mocks all $https calls.
You need to use some form of end-to-end testing to test with real data. I suggest http://angular.github.io/protractor/#/.
Related
I am using AngularJS 1.7 with Karma and Jasmine. And I have started learning Unit Test cases.
I have a sample method below in my controller
_this.method = function () {
Service.getData().then(function (response) {
if (response.productId === "ClientAPI") {
// Some code
}
else {
// Some Code
}
}, function (error) {
_this.inProgress = false;
if (error.status === 400) {
// Some Code
} else {
// Some Code
}
})
}
Below is my test case :
describe('Some Route :: Controller => ', function () {
var $componentController;
var Service;
beforeEach(module('app'));
beforeEach(inject(function (_$componentController_, _Service_) {
Service = _Service_;
spyOn(Service, 'getData').and.callFake(function() {
var deferred = $q.defer();
var response = {};
response.productId = "ClientAPI";
deferred.resolve(result);
return deferred.promise;
});
ctrl = $componentController('controllerName', { Service: Service });
}));
it('Ctrl Method : should true', function () {
ctrl.method();
expect(Service.getData).toHaveBeenCalled();
Service.getData().then(function (response) {
expect(response.productId).toBe("ClientAPI")
})
});
});
But my branch coverage is not showing for this condition if (response.productId === "ClientAPI") {
Not sure what I am doing wrong while testing in a promise.
You need to call $scope.$apply() to trigger the call of the promise callbacks:
beforeEach(inject(function (_$componentController_, _Service_) {
Service = _Service_;
spyOn(Service, 'getData').and.returnValue($q.resolve({ productId: 'ClientAPI' }));
ctrl = $componentController('controllerName', { Service: Service });
}));
it('Ctrl Method : should true', inject(function($rootScope) {
ctrl.method();
expect(Service.getData).toHaveBeenCalled();
$rootScope.$apply();
// now test that the ctrl state has been changed as expected.
// testing that the service has returned ClientAPI is completely useless:
// the service is a mock, and you have told the mock to return that
// this should test the component, based on what you've told the service
// to return. It's not supposed to test the mock service.
// testing what the service returns tests jasmine, not your code.
});
I have a webworker doing some work for me.
I've wrapped this into a service and this webworker is executed in a Promise.
Now I'm tesing this with Jasmine, and it seems that the promised is returned after the test has finished.
The difficulty in here is that the defer AND the webworker are both async at different points in time.
I've tried with async jasmine with done, setTimeout, $scope.$apply(). But ''deferred.resolve(e.data.filtered)'' is called after all those timers have suspended.
My angular service is like this:
'use strict';
angular.module('app.demographics').service('FilteringService', FilteringService);
FilteringService.$inject = ['$q'];
function FilteringService($q) {
this.filter = function (dataSet, filters) {
var deferred = $q.defer();
var worker = new Worker('my.worker.js');
var filterData = {
dataSet: dataSet,
filters: filters
};
worker.postMessage(filterData);
worker.onmessage = function (e) {
if (e.data && e.data.ready) {
deferred.resolve(e.data.filtered);
}
};
worker.onerror = function (e) {
console.log("something went wrong while filtering: ", e);
deferred.reject(e);
};
return deferred.promise;
};
}
And my test is like this, which I expect to work properly, but it never comes to the expect.
'use strict';
describe('FilteringService: ', function () {
var filteringService, $q,
dataSet = [{a: 1, b: 2}, {c: 3, d: 4}],
filters = [];
beforeEach(function () {
module('app.demographics');
inject(function (_$rootScope_, _FilteringService_, _$q_) {
filteringService = _FilteringService_;
$q = _$q_;
});
});
it('should return a promise on filtering', function () {
var filteringPromise = filteringService.filter(dataSet, filters);
filteringPromise.then(function (data) {
expect(data.length).toEqual(dataSet.length);
}, function (failure) {
fail(failure);
});
});
});
As mentioned in https://stackoverflow.com/a/37853075/1319998, the original test seems to be more of an integration test rather than a unit test. If you would like this to be a unit test....
You need to be able to mock the worker so you're not testing what it does. So in the service, instead of calling Worker directly, you can call $window.Worker, since $window can be easily mocked in tests.
app.service('FilteringService', FilteringService);
FilteringService.$inject = ['$window', '$q', '$rootScope'];
function FilteringService($window, $q, $rootScope) {
this.filter = function (dataSet, filters) {
var deferred = $q.defer();
var worker = new $window.Worker('my.worker.js');
...
Then in the test you can create a mocked worker, calling the attacted onmessage handler that would be called by the real worker, and testing that the promise then gets resolved with the correct value (I've left it as just testing the length, but in a real test I suspect you will need something a bit better).
describe('FilteringService: ', function () {
var $rootScope, filteringService, $q,
dataSet = [{a: 1, b: 2}, {c: 3, d: 4}],
filters = [];
var mockWorker;
var mockWindow = {
Worker: function() {
return mockWorker;
}
};
beforeEach(function () {
module('app.demographics');
module(function($provide) {
$provide.value('$window', mockWindow);
});
inject(function (_$rootScope_, _FilteringService_, _$q_) {
$rootScope = _$rootScope_;
filteringService = _FilteringService_;
$q = _$q_;
});
mockWorker = {
postMessage: jasmine.createSpy('onMessage')
}
});
it('when onmessage from worker called, resolves returned promise with filtered list', function () {
expect(mockWorker.postMessage).not.toHaveBeenCalled();
expect(mockWorker.onmessage).not.toEqual(jasmine.any(Function));
var filteringPromise = filteringService.filter(dataSet, filters);
expect(mockWorker.postMessage).toHaveBeenCalled();
expect(mockWorker.onmessage).toEqual(jasmine.any(Function));
mockWorker.onmessage({
data: {
ready: true,
filtered: dataSet
}
});
var result;
filteringPromise.then(function(_result) {
result = _result;
});
$rootScope.$apply();
expect(result.length).toEqual(dataSet.length);
});
});
Note you then need the $apply in the test (but not the service), to make sure the promise callbacks get called.
You can see this working at https://plnkr.co/edit/g2q3ZnD8AGZCkgkkEkdj?p=preview
I accept this isn't the optimal solution, more of a hack probably, but this is how I got Jasmine working with Angular. My approach was to create a function digestIt that takes the done function provided by Jasmine and invokes $digest using setInterval and returns a cleanup function.
function digestIt($rootScope, done) {
var intervalId: number,
_done = function() {
if (angular.isDefined(intervalId))
clearInterval(intervalId);
intervalId = null;
done();
},
_interval = function () {
if (angular.isNumber(intervalId)) {
try {
$rootScope.$digest();
} catch (e) {
_done();
}
}
},
intervalId = setInterval(_interval, 1);
return _done;
}
Here's the usage pattern.
describe("MyService ", function() {
var $rootScope,
$injector
;
beforeEach(angular.mock.inject(function (_$rootScope_, _$injector_) {
$rootScope = _$rootScope_.$new();
$injector = _$injector_;
}));
it("My Test", function (done) {
var $docs = $injector.get('MyService'),
completed = digestIt($rootScope, done)
;
$docs.asyncCall().then(function () {
/* expect */
}).catch(function() {
/* fail */
}).finally(function () {
completed();
});
});
});
It looks likes (at least in the testing environment), $q promises only get resolved (as in, their success/failure callbacks are called) when a digest cycle gets initiated. So in the service you can put in $rootScope.apply() to trigger this:
worker.onmessage = function (e) {
if (e.data && e.data.ready) {
$rootScope.$apply(function() {
deferred.resolve(e.data.filtered);
});
}
};
worker.onerror = function (e) {
console.log("something went wrong while filtering: ", e);
$rootScope.$apply(function() {
deferred.reject(e);
});
};
And then your test can be asynchronous:
it('should return a promise on filtering', function (done) {
var filteringPromise = filteringService.filter(dataSet, filters);
filteringPromise.then(function (data) {
expect(data.length).toEqual(dataSet.length);
done();
}, function (failure) {
fail(failure);
});
});
This can be seen at https://plnkr.co/edit/D21EhoCXIbj8R0P9RY40?p=preview
Note: this is probably classified as an integration test rather than a unit test, as you're testing both FilteringService and your worker together. If you were to only have a unit test, you can probably avoid the addition of $rootScope.$apply() in FilteringService by mocking the worker. You would probably also then be able to make is a synchronous test.
I am new to angularjs unit testing. I have a factory I am trying to spy on with jasmine and I can't figure out the syntax for the test spec. Below is the factory:
app.factory('assetFactory', function ($http) {
var baseAddress = "../api/";
var url = "";
var factory = {};
factory.getAssets = function (term) {
url = baseAddress + "asset/search/" + term;
return $http.get(url);
};
return factory;
});
Here is my test spec, which fails on the expect statement (Error: Expected spy getAssets to have been called):
describe('assetFactory', function () {
beforeEach(function () {
module('fixedAssetApp');
});
beforeEach(inject(function (assetFactory) {
spyOn(assetFactory, 'getAssets').and.callThrough();
}));
it('should be defined', inject(function (assetFactory) {
expect(assetFactory).toBeDefined();
}));
it('should have been called, inject(function (assetFactory) {
expect(assetFactory.getAssets).toHaveBeenCalled();
}));
});
Please add this change.
beforeEach(inject(function (assetFactory) {
spyOn(assetFactory, 'getAssets').and.callThrough();
assetFactory.getAssets();
}));
In order to toHaveBeenCalled() return true, you must called your function either in beforeEach or it block.
Ok here is the scenario we are trying to unit test with Jasmine. We have a service defined similar to the service below:
(function () {
'use strict';
angular.module('mymodule')
.service('myservice', myservice);
myservice.$inject = ['$q', '$resource', 'progressService',
'myservice2', 'myservice3', 'ngDialog'];
function myservice($q, $resource, progressService,
myservice2, myservice3, ngDialog) {
var self = this;
self.dataListSvc2 = [];
self.dataListSvc3 = [];
self.dataFromResource = null;
self.myRoutine = myRoutine;
var myResource = $resource('/someurl/webapi/GetData');
//TRYING TO TEST THIS ROUTINE!!!
function myRoutine(param1, param2) {
return progressService.show($q.all([
myResource.get({ Param1: param1 }).$promise.then(function (response) {
self.dataFromResource = response;
}),
myRoutine2(param2),
myRoutine3(param2)
]));
}
function myRoutine2(param) {
return myservice2.getSomeData(param).then(function (response) {
var results = [];
self.dataListSvc2 = [];
response.forEach(function (item) {
item.AddField1 = null;
item.AddField2 = false;
results.push(item);
});
self.dataListSvc2 = results;
});
}
function myRoutine3(param) {
return myservice3.getSomeMoreData(param, [6])
.then(function (response) {
self.dataListSvc3 = response;
});
}
}
})();
I am trying to write a unit test for myservice.myRoutine() and not having any luck, I suspect it is due to the $q.all array of promises. Here is a test I settled on but it is not ideal and honestly I don't feel like it's testing anything of value. I have also tried "mocking" the three requests with $httpBackend with no luck as all three responses come back in an array with undefined values. I searched around SO and the web and I am only finding $q.all([]) unit tests referencing controllers but not services. If anyone has some input it would be much appreciated. Here is where I settled for the time being:
describe('My Service: ', function () {
var $httpBackend;
beforeEach(module('mymodule'));
beforeEach(inject(function ($injector) {
// Set up the mock http service responses
$httpBackend = $injector.get('$httpBackend');
}));
it('Can call myroutine from myservice', inject(function (myservice) {
//Arrange
var expectedVal1 = 1234;
var expectedVal2 = 1234;
spyOn(myservice, "myRoutine");
//Act
myservice.myRoutine(expectedVal1, expectedVal2);
//Assert
expect(myservice.myRoutine).toHaveBeenCalled();
}));
});
Use the $httpBackend.when('GET', '/someurl/webapi/GetData').respond(data); to fake the http requests. You should fake the myservice2.getSomeData method too and the myservice3.getSomeMoreData I guess those methods create http request too so you could fake them in the same manner.
expect(myservice.myRoutine).toHaveBeenCalled();
Is irrelevant as you called it manually ;-).
var fixture = {/*some data ... */};
it('Can call myroutine from myservice', inject(function (myservice) {
$httpBackend.when('GET', '/someurl/webapi/GetData').respond(fixture);
//Act
myservice.myRoutine(expectedVal1, expectedVal2);
$rootScope.$apply()
//Assert
expect(myservice.dataFromResource).toBe(fixture);
}));
I have a service that i wrote for a project i am currently working on and i am trying to write a unit test for it. Below is the code for the service
angular.module('services').factory('Authorization', ['$q', 'Refs', function($q, Refs) {
function isAuthorized() {
var deferred = $q.defer();
var authData = Refs.rootRef.getAuth();
var adminRef;
if(authData.google) {
adminRef = Refs.rootRef.child('admins').child(authData.uid);
} else {
adminRef = Refs.rootRef.child('admins').child(authData.auth.uid);
}
adminRef.on('value', function(adminSnap) {
deferred.resolve(adminSnap.val());
});
return deferred.promise;
}
return {
isAuthorized: isAuthorized
};
}]);
I have written a unit test for it but anytime i run the test i get this error message ' Error: Timeout - Async callback was not invoked within timeout specified by jasmine.DEFAULT_TIMEOUT_INTERVAL.'
Below is the code for the unit test i wrote for the service:
'use strict';
describe('Authorization Service', function() {
var Refs, $q, Authorization, authData, scope, deferred;
beforeEach(angular.mock.module('Sidetime'));
beforeEach(inject(function(_Refs_, $rootScope, _$q_, _Authorization_) {
Refs = _Refs_;
$q = _$q_;
Authorization = _Authorization_;
}));
iit('return admin object', function(done) {
var result;
Authorization.isAuthorized = function() {
deferred = $q.defer();
authData = {google: 'uid:112222'};
if(authData.google) {
deferred.resolve(authData);
}
return deferred.promise;
};
Authorization.isAuthorized().then(function(result) {
console.log('result', result);
expect(result).toEqual(authData);
//done();
});
});
});
I am not sure I am writing the unit test properly. I will appreciate if someone could show be a better way of writing the unit test for this service. Thanks for your anticipated assistance.
Here is a working plunkr, with slight modifications as I don't have the code to all of your dependencies:
angular.module('services', []).factory('Authorization', ['$q', function($q) {
function isAuthorized() {
var deferred = $q.defer();
deferred.resolve({authData: {google: 'uid: 1122222'}});
return deferred.promise;
}
return {
isAuthorized: isAuthorized
};
}]);
describe('Authorization Service', function() {
var $q, Authorization, scope, deferred;
beforeEach(angular.mock.module('services'));
beforeEach(inject(function($rootScope, _$q_, _Authorization_) {
$q = _$q_;
scope = $rootScope.$new();
Authorization = _Authorization_;
Authorization.isAuthorized = function() {
deferred = $q.defer();
authData = {google: 'uid:112222'};
if(authData.google) {
deferred.resolve(authData);
}
return deferred.promise;
};
}));
it('return admin object', function(done) {
var result;
var promise = Authorization.isAuthorized();
promise.then(function(result) {
expect(result).toEqual(authData);
expect(result.google).toEqual('uid:112222e');
});
deferred.resolve(result);
scope.$digest();
});
});
(function() {
var jasmineEnv = jasmine.getEnv();
jasmineEnv.updateInterval = 250;
/**
Create the `HTMLReporter`, which Jasmine calls to provide results of each spec and each suite. The Reporter is responsible for presenting results to the user.
*/
var htmlReporter = new jasmine.HtmlReporter();
jasmineEnv.addReporter(htmlReporter);
/**
Delegate filtering of specs to the reporter. Allows for clicking on single suites or specs in the results to only run a subset of the suite.
*/
jasmineEnv.specFilter = function(spec) {
return htmlReporter.specFilter(spec);
};
/**
Run all of the tests when the page finishes loading - and make sure to run any previous `onload` handler
### Test Results
Scroll down to see the results of all of these specs.
*/
var currentWindowOnload = window.onload;
window.onload = function() {
if (currentWindowOnload) {
currentWindowOnload();
}
//document.querySelector('.version').innerHTML = jasmineEnv.versionString();
execJasmine();
};
function execJasmine() {
jasmineEnv.execute();
}
})();
http://plnkr.co/edit/DCrr6pVzF9D4OuayCZU7?p=preview
Take a look over at spies in jasmine to get a deeper introduction
I've taken Cognitroics plunkr and modified it in the way I usually write my test. Take a look at it here http://plnkr.co/edit/W5pP82CKj7tc6IO3Wj9S?p=preview
But basically what you should do is utilizing the awesomeness of spies in jasmine.
Here's how it looks like.
describe('My aweomse test suite', function(){
beforeEach(function(){
module('Awesome');
inject(function($injector){
this.MyService = $injector.get('MyService');
this.$q = $injector.get('$q');
this.$scope = $injector.get('$rootScope').$new();
spyOn(this.MyService, 'someMethod').andCallFake(function(){
var defer = this.$q.defer();
defer.resolve();
return defer.promise;
});
});
it('should do seomthing', function(){
// given
var result;
// when
this.MyServcie.someMethod().then(function(){
result = 'as promised';
});
this.$scope.$digest();
// then
expect(result).toEqual('as promised');
});
});
});