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');
Related
I've got right now a project were we need to have the backend server mocked for the time being and we are using $httpBackend on the application .run feature. I need to unit test this service that contains the $httpBackend as we will be having a vast amount of mocked calls to the server we will be covering. So right now this is what I have. As a preface to my question the current setup works when I call mockDataService.getWorkflowTask from a controller on a simple page.
My Server replacement service:
angular.module('app').run(function ($httpBackend, $resource, FakeBackendService) {
// TODO: add all necessary http intercepts.
$httpBackend.whenGET('JSON file').respond(function (method, url, data) {
var request = new XMLHttpRequest();
request.open('GET', url, false);
request.send(null);
return [request.status, request.response, {}];
});
$httpBackend.whenGET(/.*/).respond(function (method, url, data) {
return [200, FakeBackendService.getWorkflowTasks(), {}];
});
});
Here is the service for FakeBackendService:
(function () {
'use strict';
var injectParams = [];
function service(lodash) {
var vm = this;
var ret = {
getWorkflowTasks: getWorkflowTasks
};
function getWorkflowTasks() {
if (vm.workflowtasks.length < 1) {
vm.workflowtasks = loadWorkflowTasks("Some JSON file");
}
return vm.workflowtasks;
};
function loadWorkflowTasks(file) {
var workflowTasks = [];
var request = new XMLHttpRequest();
request.open("GET", file, false);
request.send(null);
if (request.status == 200) {
workflowTasks = angular.fromJson(request.response);
}
return workflowTasks;
};
function init() {
vm.workflowtasks = [];
}
init();
return ret;
}
service.$inject = injectParams;
angular.module('mock.FakeBackendService', []).service('FakeBackendService', service);
})();
So that is currently the backend server replacement mock. The following is my data handling service which contains the call to $http.get(blah blah blah).
(function () {
'use strict';
var injectParams = ['$http', '$q', 'mockConfigService', '$httpBackend'];
function factory($http, $q, configService, $httpBackend) {
var vm = this;
var factory = {
getWorkflowTask: getWorkflowTask
};
function getWorkflowTask(str) {
return getResource(str);
}
function init() {
// Get the URL we will be using to get data from
vm.dataServiceURL = configService.getDataServiceURL();
}
function getResource(baseResource) {
var resource = vm.dataServiceURL + baseResource;
return $http.get(resource).then(function (response) {
if (typeof response.data == 'object') {
// Got valid response
return $q.resolve(response.data);
}
else {
// Invalid response
return $q.reject(response.data);
}
}, function (response) {
// Something went wrong
return $q.reject(response.data);
});
}
init();
return factory;
};
factory.$inject = injectParams;
angular.module('mock.dataService', []).factory('mockDataService', factory);
}());
Now for the Jasmine-Karma Unit test.
describe("HTTP Backend Mock testing", function () {
beforeEach(angular.mock.module("app"));
beforeEach(angular.mock.module("mock.FakeBackendService"));
beforeEach(angular.mock.module("mock.configService"));
beforeEach(angular.mock.module("mock.dataService"));
it("Get the workflow task", angular.mock.inject(function (mockDataService) {
var valid = "";
var promise = mockDataService.getWorkflowTask('http://localhost/foo');
promise.then(function (response) {
valid = "Success";
}, function (response) {
valid = "Failure";
});
expect(valid).toBe("Success");
}));
});
Now to the question. So, I'll start by saying I'm new to the AngularJS world and even more so to Jasmine. Anyways, when I debug the unit test I find that the promise's status is still 0 and I always get expected '' to be 'Success' telling my I never resolve (hopefully I'm using the right lingo) the promise from the $http service in mockDataService. I've tried playing around with it some and tried to see if anyone has done this kind of a thing before. I found plenty of examples where the $httpBackend is mocked in the test but none like what I'm attempting. Any ideas or suggestions would be great. Thanks.
EDIT got a slightly working solution
So I decided that I'd by pass the run() service and just do the same response in the expectGET().respond().
describe("HTTP Backend Mock testing", function () {
beforeEach(angular.mock.module("app"));
beforeEach(angular.mock.module("mock.FakeBackendService"));
beforeEach(angular.mock.module("mock.configService"));
beforeEach(angular.mock.module("mock.dataService"));
it("Get the workflow task", angular.mock.inject(function (mockDataService, $httpBackend, FakeBackendService) {
var valid = "";
$httpBackend.expectGET('http://server:80/api/foo').respond(200, FakeBackendService.getWorkflowTasks());
var promise = mockDataService.getWorkflowTask('foo');
promise.then(function (response) {
valid = "Success";
}, function (response) {
valid = "Failure";
});
$httpBackend.flush();
expect(valid).toBe("Success");
}));
});
This sort of solves my testing problem with the run() as the goal was to verify 1) That the regex matching call the correct FakeBackendService and 2) That FakeBackendService returns correct file and actually loads it. I think I can do that by mimicking the same regex in the expectGET. However, I'll leave open for a bit to see if anyone knows how to get the run() to work.
The promise is not going to resolve unless you force it to do so before the test ends. Here is one such way to do it:
$httpBackend.expectGET(......).respond(200, 'abc');
var promise = mockDataService.getWorkflowTask('http://localhost/foo');
promise.then(function (response) {
valid = "Success";
}, function (response) {
valid = "Failure";
});
//new code here
$httpBackend.flush();
expect(valid).toBe("Success");
This will force the promise to resolve and your test should pass. You'll also need to inject the $httpBackend service into the test.
angular.module('mock.dataService', [])
.service('mockDataService', function($http) {
this.getWorkflowTask = function(url) {
return $http.get(url)
}
})
describe('HTTP Backend Mock testing', function() {
var $httpBackend
beforeEach(angular.mock.module("mock.dataService"));
beforeEach(inject(function(_$httpBackend_) {
$httpBackend = _$httpBackend_
}))
it("Get the workflow task", angular.mock.inject(function(mockDataService) {
$httpBackend.expectGET('http://localhost/foo').respond(200);
var promise = mockDataService.getWorkflowTask('http://localhost/foo');
promise.then(function(response) {
valid = "Success";
}, function(response) {
valid = "Failure";
});
$httpBackend.flush();
expect(valid).toBe("Success");
}));
})
<link href="//safjanowski.github.io/jasmine-jsfiddle-pack/pack/jasmine.css" rel="stylesheet" />
<script src="//safjanowski.github.io/jasmine-jsfiddle-pack/pack/jasmine-2.0.3-concated.js"></script>
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular-mocks.js"></script>
I have some code that runs a timer when user is active and broadcasts and event to keep the session alive.
In the controller I have some code to listen for that event and refresh the session.
I want to test this listener
$scope.$on('Keepalive', function () {
//every 45 minutes make a call to refresh the session.
var promise = authService.keepAliveSession();
promise.then(function(userPreferenceData) {
dataTransfer.setUserPref(userPreferenceData);
}, function(error) {
console.log("promise error!!"+error);
});
});
my keepalive service returns a promise which will get resolved after the httpbackend returns.
factory.keepAliveSession = function () {
var deferred = $q.defer();
req=...some stuff...
$http(req)
.success(
function (data, status) {
if ( data.user ) {
// Received data about the logged in user
deferred.resolve(factory.userPreferenceData);
} else {
// User is not authenticated; redirect to login
$window.location = data.redirect;
}
}
).error(function (error) {
// Error in oAuth refresh service
deferred.reject("Error in session keepalive"+ error);
});
return deferred.promise;
}
here is the test
it('Test for keepalive', function() {
console.log('starting for keepalive...');
httpBackend.when('POST', "http://example.com/refreshSession").respond(getMocks().response.oAuthResponseExternal);
spyOn(authServiceMock, "keepAliveSession").and.callThrough();
spyOn(dataTransferMock, "setUserPref").and.callThrough();
rootScope.$broadcast('Keepalive');
expect(authServiceMock.keepAliveSession).toHaveBeenCalled();
rootScope.$digest;
expect(dataTransferMock.setUserPref).toHaveBeenCalled();
});
The first assertion is successful, but the second one (after the digest) fails.
How can I force the promise to be resolved or rejected?
Do I need to somehow mock the $q?
It's hard to say for sure without seeing the mock implementations, how you wire up the module and how you create the controller.
My guess is that the promise that is returned from authServiceMock.keepAliveSession never gets resolved, which would lead to that the success function that dataTransfer.setUserPref lives in never gets executed.
If you for example have a mock that looks like this:
var deferred;
var authServiceMock = {
keepAliveSession: function () {
deferred = q.defer();
return deferred.promise;
}
};
You need to manually in your test either resolve or reject the promise before you trigger the digest cycle, depending on which case you are testing:
expect(authServiceMock.keepAliveSession).toHaveBeenCalled();
deferred.resolve('something');
$rootScope.$digest();
expect(dataTransferMock.setUserPref).toHaveBeenCalled();
Note that you need to execute the $digest function, in your example you just have rootScope.$digest;.
On another note, it seems to me you are mixing testing concerns a bit.
From my point of view, this is what your controller should test:
When the Keepalive event is fired - authService.keepAliveSession should be called
If the promise from authService.keepAliveSession is resolved - dataTransfer.setUserPref should be called and pass the correct data
If the promise from authService.keepAliveSession is rejected - an error message should be logged
The implementation details of the services shouldn't matter (other than that authService.keepAliveSession returns a promise) and you shouldn't need to involve httpBackend in this case.
Setting up fake return data with httpBackend should be used when you test the actual service that uses the $http service.
Below is an alternative way to test this, using spyOn and callFake instead of using mock implementations.
describe('myApp', function() {
var $rootScope;
var $controller;
var authService;
var dataTransfer;
var $log;
var myController;
beforeEach(function() {
module('myApp');
inject(function(_$rootScope_, _$controller_, _authService_, _dataTransfer_, _$q_, _$log_) {
$rootScope = _$rootScope_;
$controller = _$controller_;
authService = _authService_;
dataTransfer = _dataTransfer_;
$q = _$q_;
$log = _$log_;
});
myController = $controller('MyController', {
$scope: $rootScope.$new()
});
});
it('On event "Keepalive" - should call "authService.keepAliveSession"', function() {
spyOn(authService, 'keepAliveSession').and.callFake(function() {
var deferred = $q.defer();
return deferred.promise;
});
$rootScope.$broadcast('Keepalive');
expect(authService.keepAliveSession).toHaveBeenCalled();
});
it('Promise from "authService.keepAliveSession" is resolved - should call "dataTransfer.setUserPref"', function() {
var data = {};
spyOn(authService, 'keepAliveSession').and.callFake(function() {
var deferred = $q.defer();
deferred.resolve(data);
return deferred.promise;
});
spyOn(dataTransfer, 'setUserPref');
$rootScope.$broadcast('Keepalive');
$rootScope.$digest();
expect(dataTransfer.setUserPref).toHaveBeenCalledWith(data);
});
it('Promise from "authService.keepAliveSession" is rejected - should not call "dataTransfer.setUserPref"', function() {
var data = {};
spyOn(authService, 'keepAliveSession').and.callFake(function() {
var deferred = $q.defer();
deferred.reject(data);
return deferred.promise;
});
spyOn(dataTransfer, 'setUserPref');
$rootScope.$broadcast('Keepalive');
$rootScope.$digest();
expect(dataTransfer.setUserPref).not.toHaveBeenCalled();
});
it('Promise from "authService.keepAliveSession" is rejected - should log message', function() {
var error = {};
spyOn(authService, 'keepAliveSession').and.callFake(function() {
var deferred = $q.defer();
deferred.reject(error);
return deferred.promise;
});
spyOn($log, 'log');
$rootScope.$broadcast('Keepalive');
$rootScope.$digest();
expect($log.log).toHaveBeenCalledWith(error);
});
});
Demo: http://plnkr.co/edit/L1uks0skH2N5bAXGoe90?p=preview
I am calling an API service which returns a promise from a factory.
Here is a part of my factory.
factories.factory('OnBoardingFactory', ['$http',
function ($http) {
var dataFactory = {};
dataFactory.get = function (url) {
return $http.get('http://localhost/api/onboarding/' + url)
};
return dataFactory
}
]);
And here is where its called from the controller:
OnBoardingFactory.get('login?username=test&password=password')
.then(function(response){
$scope.response = response.status;
})
This returns data in the controller absolutely fine. However I have difficulties when I come to test it. Here is my test script:
var scope, FakeOnBoardingFactory, controller, q, deferred;
beforeEach(module('app.module'));
beforeEach(function () {
FakeOnBoardingFactory = {
get: function () {
deferred = q.defer();
// Place the fake return object here
deferred.resolve({ response: {status: 200}});
return deferred.promise;
}
};
spyOn(FakeOnBoardingFactory, 'get').and.callThrough();
});
beforeEach(inject(function ($q, $rootScope, $controller, $injector ) {
scope = $rootScope.$new();
q = $q;
controller = $controller(OnBoardingCtrl, {
$scope: scope,
OnBoardingFactory: FakeOnBoardingFactory
})
}));
it('Should call the form and return 200', function () {
// Execute form
scope.loginCredentials({$valid: true});
scope.$apply();
// Ensure script is called (which passes fine)
expect(FakeOnBoardingFactory.get).toHaveBeenCalled();
scope.$apply();
// BREAKS HERE
expect(scope.status).toBe(200);
})
When expect(FakeOnBoardingFactory.get).toHaveBeenCalled(); is called, this passes fine. However then I run expect(scope.status).toBe(200), it breaks "Expected undefined to be 200".
This would indicate that my FakeOnBoardingFactory isn't returning any data. But I can't seem to find the issue.
It must be the change to support multiple body assertions that has caused this bug.
The workaround for now is to either don't use expect and do your assertion in the end function callback.
So instead of .expect(200) it would be.
end(function(err,res) { res.status.should.equal(200) },
or if you do use expect.. you need to make sure you specify a body as well as just a status..
it('should assert status only 1', function(done){
var app = express();
app.get('/user', function(req, res){
res.send(201, { name: 'tobi' }); }); request(app) .get('/user')
.expect('Content-Type', /json/)
.expect('Content-Length', '20')
.expect(201)
.end(function(err, res){
if (err) throw err;
});
})
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();
});
});
});
Been trying to wrap my head around this angular-mocks jasmine testing for a couple of days now.. Got some basic stuff working but when wanting to spy and assert stuff on mocks i get stuck.
TEST CODE
describe("My service tests: calls correct urls and resolves", function () {
var $myService, $httpBackend, $q, deffered;
beforeEach(module('myModule', function ($provide) {
var mockEndpointService = {
getApiEndpoint: function () {
return 'mockEndPoint';
}
};
$provide.value('$endpointService', mockEndpointService);
}));
beforeEach(inject(function (_$myService_) {
$myService= _myService_;
}));
beforeEach(inject(function ($injector) {
$httpBackend = $injector.get('$httpBackend');
$q = $injector.get('$q');
deffered = $q.defer();
spyOn($q, 'defer').andReturn(deffered);
spyOn(deffered, 'resolve');
$httpBackend.when('GET', 'mockEndPoint/testtest').respond(123);
}));
it("Get calls correct URL and resolves deffered on success", function () {
$myService.get('testtest');
$httpBackend.expect('GET', 'mockEndPoint/testtest');
$httpBackend.flush(); //Added this
expect(deffered.resolve).toHaveBeenCalledWith(123); //This assert fails
});
});
SERVICE CODE
myModule.factory('$myModule', ['$http', '$q', '$endpointService', function ($http, $q, $endpointService) {
var apiPath = $endpointService.getApiEndpoint('MyApi');
this.get = function (id) {
var deferred = $q.defer();
var url = apiPath + '/' + id;
console.log('GET: ' + url);
$http.get(url).success(function (data) {
deferred.resolve(data);
}).error(function () {
deferred.reject('error: could not load thingy with id: ' + id);
});
return deferred.promise;
};
}]);
So question one, i have 3 beforeEach blocks, can i merge some of them? I get all kinds of faults when i try to do it.
Also, how would i go about to assert that the resolved is called on success of the get?
You see what I have tried with above, but it does not work.
Thx in advance.
Edit:
Adding the flush seems to make some difference but not the expected result, so any help regarding promises and defer is really appreciated.
Edit2:
Changed title to actually reflect what i had problems with.
//TWD
Remove $httpBackend.when from the last beforeEach block, and the test should look like the following:
it("Get calls correct URL and resolves deffered on success", function () {
$httpBackend.expect('GET', 'mockEndPoint/testtest').respond(123);
$myService.get('testtest');
$httpBackend.flush();
expect(deffered.resolve).toHaveBeenCalledWith(123);
});
$httpBackend.flush must be called to resolve the promise of $http.get. Only after that, your deferred object's resolve would be called.
Update:
I think it's a bad idea to spy on a vendor function to begin with, it's the last thing you want to do when you're unit testing your code. And, in your service code, it's not necessary to create a new deferred object, since $http.get will return a promise to you, so you can simplify the code like the following:
this.get = function (id) {
var url = apiPath + '/' + id;
console.log('GET: ' + url);
return $http.get(url).success(function (data) {
return data;
}).error(function () {
return 'error: could not load thingy with id: ' + id;
});
};
Now, you can test it like this:
it("Get calls correct URL and resolves deffered on success", function () {
$httpBackend.expect('GET', 'mockEndPoint/testtest').respond(123);
$myService.get('testtest').then(function(data) {
expect(data).toEqual(123);
});
$httpBackend.flush();
});