I have created an angular service which I am testing without any issue, I then started trying to inject a dependency into the tests when I started having an issue.
I want to make sure that a function in the dependency has been called but it's coming back undefined.
Here's my service:
angular.module('enigmaApp.services', [])
.factory('auth', function($window, $http, SS){
var auth = {};
auth.register = function(user){
return $http.post('/register', user)
.success(function(data){
if(data.token){
SS.setObj('appToken', data.token);
}
});
};
return auth;
});
My test:
describe('Auth Service Tests', function () {
var $httpBackend, auth, defer, registerReqHandler, setObjSpy, SS, SSMock, user;
beforeEach(module('enigmaApp'));
beforeEach(function () {
// Create spies
setObjSpy = jasmine.createSpy('setObj');
SSMock = {
setObj: setObjSpy
}
SS = SSMock;
module(function ($provide) {
$provide.value('SS', SSMock);
});
});
beforeEach(inject(function (_$httpBackend_, $injector, $q) {
$httpBackend = _$httpBackend_;
defer = $q.defer();
registerReqHandler = $httpBackend.when('POST', '/register').respond(defer.promise);
auth = $injector.get('auth');
}));
afterEach(function () {
$httpBackend.flush();
$httpBackend.verifyNoOutstandingExpectation();
$httpBackend.verifyNoOutstandingRequest();
});
describe('auth.register(user)', function () {
beforeEach(function () {
user = {
email: 'bwayne#wayneenterprise.com',
first_name: 'Bruce',
last_name: 'Wyane',
password: 'password123'
};
});
it('should call SS.setObj on successful registration', function () {
$httpBackend.expectPOST('/register').respond({ token: 'fakeToken' });
auth.register(user);
expect(SS.setObj).toHaveBeenCalled();
});
});
When I run the tests I get a failed test that says "Expected spy setObj to have been called." Any idea on what I'm doing wrong here? I thought I set up a mock for SS.setObj and provided it to the module,
One problem you have is that it seems you aren't actually returning your auth object in your factory:
angular.module('enigmaApp.services', [])
.factory('auth', function($window, $http, SS){
var auth = {};
auth.register = function(user){
return $http.post('/register', user)
.success(function(data){
if(data.token){
SS.setObj('appToken', data.token);
}
});
};
// need to return auth here...
return auth;
});
update
Move your flush call into your it block
it('should call SS.setObj on successful registration', function () {
$httpBackend
.expectPOST('/register')
.respond({ token: 'fakeToken' });
auth.register(user);
$httpBackend.flush();
expect(SS.setObj).toHaveBeenCalled();
});
afterEach blocks are for clean up. Jasmine assumes your test is over by that point so your flush() call won't have any effect.
Related
I am trying to test $http calls inside services which, upon $http response, store the response data in service itself (does not return response to controller). Most examples I found (even AngularJs documentation) are testing $http calls in controller. For ex:
app.factory('dataFactory', function($http){
return {
getData: function(){
return $http.get('https://some-url');
}
}
});
app.controller('MainCtrl', function($scope, dataFactory, $http) {
$scope.name = 'World';
dataFactory.getData().success(function(data){
$scope.data = data;
})
});
The unit test for this code is:
describe('with httpBackend', function() {
beforeEach(inject(function($controller, $rootScope, $httpBackend) {
$scope = $rootScope.$new();
$httpBackend.when('GET', 'https://some-url')
.respond({things: 'and stuff'});
MainCtrl = $controller('MainCtrl', { $scope: $scope });
$httpBackend.flush();
}));
it('should set data to "things and stuff"', function() {
expect($scope.data).toEqual({
things: 'and stuff'
});
});
});
But in my services, I am making the calls in following fashion:
app.service('dataService', function($http) {
var self = this;
this.getData = function() {
$http.get('https://some-url/')
.then(
function success(response) {
self.data = response.data
},
function error(msg) {
console.log(msg);
}
);
};
});
For this, I will need to unit test the service and not the controller.
EDIT: Below is the unit-test I've written (which is passing, but not sure it's the correct approach):
describe('.getData()', function() {
beforeEach(inject(function($httpBackend) {
$httpBackend.when('GET', 'https://some-url/')
.respond({data: 'sample data'});
dataService.getData();
$httpBackend.flush();
}));
it('should store data correctly', function() {
expect(dataService.data).toEqual({data: 'sample data'});
});
});
Need some help regarding the unit-testing approach I should follow to test services with $http calls (and store data).
Can someone please tell me the best way to run tests on my controller function getData and the factory function too. I've very confused and don't know where to start. How would you write tests for the code below?
myApp.controller('myController', ['$scope', 'myFactory', function ($scope, myFactory) {
$scope.getData = function(id) {
var promise = myFactory.GetData('/dta/GetData?Id=' + id);
promise
.then(function (success) {
$scope.result = success;
}, function (error) {
$scope.error = true;
});
}
});
myApp.factory('myFactory', ['$http', function ($http) {
return {
GetData: function (url) {
return $http.get(url)
.then(function (response) {
return response.data;
}, function (error) {
return error;
});
}
}
}]);
You'll want to test each component in isolation (that's what unit tests are for). So something like this for the controller
describe('myController test', () => {
let scope, myFactory;
beforeEach(() => {
myFactory = jasmine.createSpyObj('myFactory', ['GetData']);
module('your-module-name');
inject(function($rootScope, $controller) {
scope = $rootScope.$new();
$controller('myController', {
$scope: scope,
myFactory: myfactory
});
});
});
it('getData assigns result on success', inject(function($q) {
let id = 1, success = 'success';
myFactory.GetData.and.returnValue($q.when(success));
scope.getData(id);
expect(myFactory.GetData).toHaveBeenCalledWith('/dta/GetData?Id=' + id);
scope.$digest(); // resolve promises
expect(scope.result).toBe(success);
}));
it('getData assigns error on rejections', inject(function($q) {
myFactory.GetData.and.returnValue($q.reject('error'));
scope.getData('whatever');
scope.$digest();
expect(scope.error).toEqual(true);
}));
});
For your factory, you would create a separate describe and inject and configure $httpBackend. There are plenty of example in the documentation.
FYI, you should omit the error handler in your factory, ie
return $http.get(url).then(response => response.data);
or if you don't like ES2015
return $http.get(url).then(function(response) {
return response.data;
});
as you are currently converting a failed request into a successful promise.
In fact, I'd go a bit further to make your GetData factory more useful than a mere $http wrapper
GetData: function(id) {
return $http.get('/dta/GetData', {
params: { Id: id }
}).then(function(res) {
return res.data;
});
}
This is a function in my controller which uses Toastr for notifications. How would I test Toastr in my Jasmine unit test for this function.
$scope.login = function(user) {
$scope.user = user;
MyAuthService.login($scope.user)
.then(function(response) {
MyConfig.setUser(response.data.data);
toastr.success('Welcome', 'Login!',{closeButton: true});
});
}
As you are using promises you should use $q to mock myAuthService.login to return a resolved promise. You also want to spy on toastr.success and MyConfig.setUser. After calling $scope.login() you need to resolve the resolved promise and then call $rootScope.$digest();:
describe('MyCtrl', function() {
var createController, $scope, $rootScope, myAuthService, myConfig, toastr, deferred;
beforeEach(module('app'));
beforeEach(inject(function($controller, _$rootScope_, $q) {
$rootScope = _$rootScope_;
deferred = $q.defer();
myConfig = {
setUser: function (data) {
}
};
spyOn(myConfig, 'setUser');
myAuthService = {
login: function () {
}
};
spyOn(myAuthService, 'login').and.returnValue(deferred.promise);
toastr = {
success: function (message, title, options) {
}
};
spyOn(toastr, 'success');
$scope = $rootScope.$new();
createController = function() {
return $controller('MyCtrl',
{
$scope: $scope,
MyAuthService: myAuthService,
MyConfig: myConfig,
toastr: toastr
});
};
}));
it('login sets user in config and shows success toastr', function() {
//Arrange
createController();
var response = {
data: {
data: {
username: 'test'
}
}
};
$scope.user = {
username: 'test'
};
//Act
$scope.login();
deferred.resolve(response);
$rootScope.$digest();
//Assert
expect(myAuthService.login).toHaveBeenCalledWith($scope.user);
expect(myConfig.setUser).toHaveBeenCalledWith(response.data.data);
expect(toastr.success).toHaveBeenCalledWith('Welcome', 'Login!', {closeButton: true});
});
});
Plunkr
Need help on Jasmine with angularjs :
I created spec for this below angular code. I could able to work with the service, if i don't have a data1 function. I need to know how we do spyOn with a function referred in a async call.
'use strict';
angular.module('myApp.services', [])
.factory("exampleService", function ($http) {
var data1 = function () {
return '/test';
}
return {
data: function () {
return data1();
},
getData: function () {
return $http.get("/exampleUrl" + data1());
}
}
});
Below spec code is
'use strict';
describe('service', function () {
var $httpBackend;
beforeEach(module('myApp.services'));
beforeEach(inject(function ($injector) {
$httpBackend = $injector.get("$httpBackend");
$httpBackend.when("GET", "/exampleUrl/test1")
.respond(200, {
value: "goodValue"
});
}));
afterEach(function () {
$httpBackend.flush();
$httpBackend.verifyNoOutstandingExpectation();
$httpBackend.verifyNoOutstandingRequest();
});
describe('exampleService successful http request', function () {
it('.value should be "goodValue"', inject(function (exampleService) {
spyOn(exampleService, 'data').and.callFake(function () {
//done();
return '/test1';
});
exampleService.getData().success(function (response) {
expect(response.value).toEqual("goodValue");
}).error(function (response) {
//should not error with $httpBackend interceptor 200 status
expect(false).toEqual(true);
});
}));
});
});
But i am getting exception like this
Error : Unexpected Request : GET /exampleUrl/test
I am trying to test a simple service for learning purposes..However; I can't figure out how it must be done:
service:
.factory('myService', function($http) {
var myService = {
async: function() {
var promise = $http.get('test.json').then(function (response)
{
return response.data;
});
return promise;
}
};
return myService;
});
controller:
myService.async().then(function(d) {
$scope.data = d;
$scope.e = $scope.data.txt;
});
test:
'use strict';
describe("myService", function(){
beforeEach(module("testingExpApp"));
var myService,
$httpBackend;
beforeEach(inject(function(myService, _$httpBackend_){
myService = myService;
$httpBackend = _$httpBackend_;
}));
describe("get", function(){
it('should return test.json data', function () {
var url = "../mock/test.json";
var x = $httpBackend.expectGET(url).respond(200, 'txt from json');
// flush response
$httpBackend.flush();
expect(x).toBe('txt from json');
});
});
});
I get 'no pending request to flush!'
I just want to test that myservice.get() get the test.json file data..I have tried everything but can't get it working..
Any tips?
Thanks a lot in advance!
What I was missing is to call service function
it had to be:
it('should return test.json data', function () {
var url = "../../mock/test.json";
$httpBackend.expectGET(url).respond(200, 'data from test.json');
//Execute service func here..
myServiceFunc.async().then(function(result) {
console.log(result);
expect(result).toEqual('data from test.json');
});
$httpBackend.flush();
});