How can I test the value of a promise, returned by a service? In the $q documentation, the promise value is preset in the test using resolve(value). Other approaches test the service logic in a controller, using the fact that AngularJS evaluates the promises and binds the values the $scope.
In my opinion, none of these approaches actually test the logic of the service in the place where it should be tested. How can I test that the resolved promise (which is returned by the service) contains the correct value?
Here an example:
myApp.service('service', function($q){
var obj = {};
obj.test = function() {
var deferred = $q.defer();
deferred.resolve(false);
return deferred.promise;
}
return obj;
});
In order to test the service, I want to do the following in theory (which does not work in practice):
var $q, service;
beforeEach(function () {
module('myModule');
service = $injector.get('service');
});
describe('...', function() {
it('Testing whether promise contains correct value', function() {
var myPromise = service.test();
myPromise.then(function(value) {
expect(value).toBeFalsy();
});
});
});
I believe you are injecting the service in a wrong way. You have to use the underscore notation. Please refer this link. http://nathanleclaire.com/blog/2014/04/12/unit-testing-services-in-angularjs-for-fun-and-for-profit/
So, your test should look something like this.
var service;
beforeEach(function () {
module('myModule');
inject(function(_service_) {
service = _service_;
});
});
describe('...', function() {
it('Testing whether promise contains correct value', function() {
var myPromise = service.test();
myPromise.then(function(value) {
expect(value).toBeFalsy();
});
});
});
Related
I cannot get the test result to pass I'm using a very basic implementation to understand testing deeper.
I have a factory which returns a promise, accessed from my controller. I want to test that the call succeeds and assigns the response to the repos var. Following is the code:
'use strict';
angular.module('app')
.factory('searchServ', function ($timeout, $q, $http) {
return {
fetch: function(user) {
var deferred = $q.defer();
$timeout(function(){
$http({method: 'GET', url: 'https://api.github.com/users/' + user + '/repos'}).then(function(repos) {
deferred.resolve(repos.data);
}, function(reason){
deferred.reject(reason.status);
console.log(reason);
});
}, 30);
return deferred.promise;
}
};
})
.controller('MainCtrl', function ($scope, searchServ) {
$scope.results = function(user) {
$scope.message = '';
searchServ.fetch(user).then(function (repos) {
if(repos.length){
$scope.message = '';
$scope.repos = repos;
}
else{
$scope.message = 'not found'
}
}, function (){
$scope.message = 'not found';
});
};
});
//Test
'use strict';
describe('MainCtrl', function () {
var scope, searchServ, controller, deferred, repos = [{name: 'test'}];
// load the controller's module
beforeEach(module('app'));
beforeEach(inject(function($controller, $rootScope, $q) {
searchServ = {
fetch: function () {
deferred = $q.defer();
return deferred.promise;
}
};
spyOn(searchServ, 'fetch').andCallThrough();
scope = $rootScope.$new();
controller = $controller('MainCtrl', {
$scope: scope,
fetchGithub: fetchGithub
});
}));
it('should test', function () {
expect(scope.test).toEqual('ha');
});
it('should bind to scope', function () {
scope.results();
scope.$digest();
expect(scope.message).toEqual('');
//expect(scope.repos).not.toBe(undefined);
});
});
Running the test gives me the following error :
TypeError: undefined is not a function (evaluating 'spyOn(searchServ, 'fetch').andCallThrough()') in test/spec/controllers/main.js (line 15)
Any idea how I can test this such that it tests the scope binding as well as the async call?
There are a lot of issues with your code.
I've created this Plunkr for the purpose. index.js is the file with your code and test cases. I've edited most of the part according to the conventions and best-practices.
There are a few pointers I wanted to give you:
Since $http returns a promise, you should use that, instead of resolving the promise and creating another promise from your method. Not sure why is timeout used. So I removed $q and $timeout from searchServ's dependencies.
I did the same in the test case by removing the deferred variable that you used.
You should be using angular-mocks.js to mock your services and other dependencies instead of defining a service inside your test case(The way you have did.)
You should create separate describe blocks for testing different parts of your code(a controller in this case).
Hope this helps!
I was reading posts related for don't repeat the question.
I have the next unit testing code:
describe('service', function() {
var questionApiService;
beforeEach(module('myApp'));
beforeEach(inject(function (_questionApiService_) {
questionApiService = _questionApiService_;
}));
// Test service availability
it('check the existence of get field question service', inject(function(questionApiService) {
//expect(1).toEqual(100);
questionApiService.getField()
.then(function(data) {
//console.log(data);
expect(1).toEqual(100);
});
}));
});
If I run the code expect(1).toEqual(100); outside the service, the result is Error, but if I write the same code expect(1).toEqual(100); inside the service, the result is Success, which makes me think that the validator is not entering the service.
Whats wrong?
EDIT 1:
Hello Asta, I think ur idea is very good and i'm trying to implement it. I have an error in my code and i don't know how do debugging:
defer = $q.defer();
spyOn(questionApiService, 'getField').andReturn(defer.promise);
defer.resolve(data);
expect(data.nextQ).toEqual(1);
My unit testing always fails. If promise is successful, the "data" object must have nextQ attribute.
EDIT 2:
Hi Asta, your code is amazing. I'm trying to execute your code in my system and still with error. The ut fails:
Error: Unexpected request: GET http://mi.url.com/api/thefield No more request expected
Do u know what's wrong? Clarify that the code works fine on my application but ut is the problem.
Question Api Service code:
angular.module('myApp.services')
.factory('questionApiService', function($http, $q) {
var myService = {
getField: function() {
var defer = $q.defer();
$http.get('http://mi.url.com/api/thefield')
.success( function(data) {
defer.resolve(data);
})
.error( function(data) {
defer.reject(data);
});
return defer.promise;
};
return myService;
});
Your test:
describe('myApp', function () {
beforeEach(function () {
module('myApp');
});
describe('questionApiService', function () {
it('should check the existence of get field question service', inject(function($rootScope, questionApiService) {
var response = null;
var promise = questionApiService.getField();
promise.then(function(data) {
response = data;
});
$rootScope.$apply();
var expectedResponse = { "nextQ": 1 };
console.log(response);
//expect(JSON.parse(response.nextQ)).toEqual(expectedResponse.nextQ);
}));
});
});
I think you just need to move your expectation outside the then and do a $rootScope.$apply().
it('should check the existence of the get field question service', inject(function($rootScope, questionApiService) {
response = null;
promise = questionApiService.getField()
promise.then(function(data) {
response = data;
});
$rootScope.$apply();
expectedResponse = { "nextQ": "value" }
expect(JSON.parse(response)).toEqual(expectedResponse);
}));
I created a jsFiddle you can use to play around with. It sets up a service that returns JSON via a promise which I used to test http://jsfiddle.net/neridum/9uumwfzc/
Alternatively if you want to test this service from another service you can mock it out using spies. Here you would mock the response as a promise and then resolve it
defer = $q.defer();
spyOn(questionApiService, 'getField').andReturn(defer.promise);
defer.resolve(data);
expect(data).toEqual('100');
I'm trying to write a karma/jasmine test and I would like some explanations about how mocks are working on a service which is returning a promise. I explain my situation :
I have a controller in which I do the following call :
mapService.getMapByUuid(mapUUID, isEditor).then(function(datas){
fillMapDatas(datas);
});
function fillMapDatas(datas){
if($scope.elements === undefined){
$scope.elements = [];
}
//Here while debugging my unit test, 'datas' contain the promise javascript object instead //of my real reponse.
debugger;
var allOfThem = _.union($scope.elements, datas.elements);
...
Here is how my service is :
(function () {
'use strict';
var serviceId = 'mapService';
angular.module('onmap.map-module.services').factory(serviceId, [
'$resource',
'appContext',
'restHello',
'restMap',
serviceFunc]);
function serviceFunc($resource, appContext, restHello, restMap) {
var Maps = $resource(appContext+restMap, {uuid: '#uuid', editor: '#editor'});
return{
getMapByUuid: function (uuid, modeEditor) {
var maps = Maps.get({'uuid' : uuid, 'editor': modeEditor});
return maps.$promise;
}
};
}
})();
And finally, here is my unit test :
describe('Map controller', function() {
var $scope, $rootScope, $httpBackend, $timeout, createController, MapService, $resource;
beforeEach(module('onmapApp'));
beforeEach(inject(function($injector) {
$httpBackend = $injector.get('$httpBackend');
$rootScope = $injector.get('$rootScope');
$scope = $rootScope.$new();
var $controller = $injector.get('$controller');
createController = function() {
return $controller('maps.ctrl', {
'$scope': $scope
});
};
}));
afterEach(function() {
$httpBackend.verifyNoOutstandingExpectation();
$httpBackend.verifyNoOutstandingRequest();
});
var response = {"elements":[1,2,3]};
it('should allow user to get a map', function() {
var controller = createController();
$httpBackend.expect('GET', '/onmap/rest/map/MY-UUID?editor=true')
.respond({
"success": response
});
// hope to call /onmap/rest/map/MY-UUID?editor=true url and hope to have response as the fillMapDatas parameter
$scope.getMapByUUID('MY-UUID', true);
$httpBackend.flush();
});
});
What I really want to do is to have my response object ( {"elements:...}) as the datas parameter of the fillMapDatas function. I don't understand how to mock all the service things (service, promise, then)
So you want to test, if your service responses as expected? Then, this is something you would rather test on the service. Unit test promise based methods could look like this:
var mapService, $httpBackend, $q, $rootScope;
beforeEach(inject(function (_mapService_, _$httpBackend_, _$q_, _$rootScope_) {
mapService = mapService;
$httpBackend = _$httpBackend_;
$q = _$q_;
$rootScope = _$rootScope_;
// expect the actual request
$httpBackend.expect('GET', '/onmap/rest/map/uuid?editor=true');
// react on that request
$httpBackend.whenGET('/onmap/rest/map/uuid?editor=true').respond({
success: {
elements: [1, 2, 3]
}
});
}));
As you can see, you don't need to use $injector, since you can inject your needed services directly. If you wanna use the correct service names throughout your tests, you can inject them with prefixed and suffixed "_", inject() is smart enough to recognise which service you mean. We also setup the $httpBackend mock for each it() spec. And we set up $q and $rootScope for later processing.
Here's how you could test that your service method returns a promise:
it('should return a promise', function () {
expect(mapService.getMapUuid('uuid', true).then).toBeDefined();
});
Since a promise always has a .then() method, we can check for this property to see if it's a promise or not (of course, other objects could have this method too).
Next you can test of the promise you get resolves with the proper value. You can do that setting up a deferred that you explicitly resolve.
it('should resolve with [something]', function () {
var data;
// set up a deferred
var deferred = $q.defer();
// get promise reference
var promise = deferred.promise;
// set up promise resolve callback
promise.then(function (response) {
data = response.success;
});
mapService.getMapUuid('uuid', true).then(function(response) {
// resolve our deferred with the response when it returns
deferred.resolve(response);
});
// force `$digest` to resolve/reject deferreds
$rootScope.$digest();
// make your actual test
expect(data).toEqual([something]);
});
Hope this helps!
I have the following service that I want to unit test. I have mocked out the personService.deletePerson function and want to test the code inside the promise.
treeApp.factory('userMediatorService', ['$q', '$dialog', 'personService', 'confirmationMessageService',
function ($q, $dialog, personService, confirmationMessageService) {
.......
deletePerson: function (personId) {
var self = this;
personService.deletePerson(personId).then(function (data) {
//how to test this?
self.closeAndRefresh();
confirmationMessageService.set('Person Deleted');
});
},
closeAndRefresh: function () {
this.closeModal();
}
......
This is my test.
beforeEach(inject(function ($injector) {
sut = $injector.get('userMediatorService');
personService = $injector.get('personService');
q = $injector.get('$q');
def = q.defer();
spyOn(personService, 'deletePerson').andReturn(
def.promise
);
}));
describe('when a person is deleted', function () {
it('should call the delete person service and close the modal', function () {
sut.deletePerson(123);
expect(personService.deletePerson).toHaveBeenCalledWith(123); //pass
expect(sut.closeAndRefresh).toHaveBeenCalled(); //fail
});
});
Can anyone point me in the right direction for testing this kind of scenario? I realise that I can remove the spy on deletePerson, but then it wouldn't test this service in isolation.
Thanks.
Update:
I've tried using andCallFake as suggested by codemonkey and I still have the issue.
spyOn(personService, 'deletePerson').andCallFake(function () {
def = q.defer();
def.resolve(true);
return def.promise;
});
You probably want to use the callFake on the spy rather than andReturn. That will let you write a function that can resolve (or reject) the promise rather than just returning the promise.
I am trying to begin writing unit tests for my angular application and hit a stopping block pretty quick as I am unsure of how exactly to mock my service in a testable way.
Is there a way to mock the REST call otherwise it would seem like I need to mirror everything within my service in my tests which doesn't seem right to me, but I am rather new to test writing so maybe this is how it is supposed to be accomplished. Any help would be greatly appreciated.
My service is as follows:
angular.module('resources.users', ['ngResource'])
.factory('User', function($resource) {
var resource = $resource('/api/index.php/users/:username', {}, {
'update': {method: 'PUT'}
});
resource.getUser = function(username, successCb) {
return resource.query({username: username}, successCb);
};
return resource;
});
My test consists thus far of:
describe('User', function() {
var mockUserResource;
beforeEach(module('resources.users'));
beforeEach(function() {
mockUserResource = sinon.stub({
getUser: function(username) {
mockUserResource.query({username: username});
},
query: function() {}
});
module(function($provide) {
$provide.value('User', mockUserResource);
})
});
describe('getUser', function() {
it('should call getUser with username', inject(function(User) {
User.getUser('test');
expect(mockUserResource.query.args[0][0]).toEqual({username: 'test'});
}));
})
});
You can mock the requests made by ngResource like this:
describe('User', function () {
var mockUserResource, $httpBackend;
beforeEach(angular.mock.module('myApp'));
beforeEach(function () {
angular.mock.inject(function ($injector) {
$httpBackend = $injector.get('$httpBackend');
mockUserResource = $injector.get('User');
})
});
describe('getUser', function () {
it('should call getUser with username', inject(function (User) {
$httpBackend.expectGET('/api/index.php/users/test')
.respond([{
username: 'test'
}]);
var result = mockUserResource.getUser('test');
$httpBackend.flush();
expect(result[0].username).toEqual('test');
}));
});
});
Demo
zsong's answer greatly helped me understand this, but I would like to expand on how it works. In case it gets edited, I list the code again here:
describe('User', function () {
var mockUserResource, $httpBackend;
beforeEach(angular.mock.module('myApp'));
beforeEach(function () {
angular.mock.inject(function ($injector) {
$httpBackend = $injector.get('$httpBackend');
mockUserResource = $injector.get('User');
})
});
describe('getUser', function () {
it('should call getUser with username', inject(function (User) {
$httpBackend.expectGET('/api/index.php/users/test')
.respond([{
username: 'test'
}]);
var result = mockUserResource.getUser('test');
$httpBackend.flush();
expect(result[0].username).toEqual('test');
}));
});
});
What's going on here?
1
beforeEach(angular.mock.module('myApp'));
We tell the Angular injector ($injector and angular.mock.inject) to inject things defined in the myApp module. You can think of it as defining a module dependency without a dependent module. Compare with how things defined in the myApp module can be injected in, say, a controller in a angular.module('myOtherApp', ['myApp']) module.
2
beforeEach(function () {
angular.mock.inject(function ($injector) {
$httpBackend = $injector.get('$httpBackend');
mockUserResource = $injector.get('User');
})
});
Before each spec, run the function ($injector) function with dependencies injected. In this case, the dependency ($injector) is resolved implicitly from the parameter name. A functionally equivalent variant of this snippet is
beforeEach(function () {
angular.mock.inject(['$httpBackend', 'User', function ($httpB, User) {
$httpBackend = $httpB;
mockUserResource = User;
}]);
});
Here we have instead declared the dependencies explicitly, and are free to use any parameter names we wish.
3
it('should call getUser with username', inject(function (User) {
Again, the test function is injected with the implicitly resolved User service as a parameter, though it isn't actually used.
Notice that this time there is no wrapper function around the inject call. inject invokes the passed function immediately if a spec is currently running, but otherwise it returns a wrapper function (see the inject docs and source code), so we don't actually need the wrapper function. Thus, we could have written the beforeEach snippet above like this:
beforeEach(angular.mock.inject(function ($injector) {
$httpBackend = $injector.get('$httpBackend');
mockUserResource = $injector.get('User');
}));