Still rather new to angular unit testing. I have a service in module 'example' that loads a local JSON file through the $http service and asynchronously returns the response data.
I figured out that I need to test (using Jasmine) that
the http GET connects with the local resource
the http service loads the JSON and gets the correct json content
the service fulfills its promise to return the response data
my service code
/**
* Service to load JSON data.
*/
.service('jsonLoader', ['$http','$timeout', 'TABLE_DATA_LOC', function($http, $timeout, TABLE_DATA_LOC) {
this.load = function() {
return $timeout(function() {
return $http.get(TABLE_DATA_LOC).then(function(response) {
return response.data;
});
}, 30);
};
what I have for the test currently:
describe('jsonLoader service', function() {
var jsonLoader, httpBackend;
beforeEach(module("example"));
beforeEach(inject(function(_jsonLoader_, $httpBackend) {
jsonLoader = _jsonLoader_;
httpBackend = $httpBackend;
}));
it("should load json", function() {
httpBackend.whenGET('./mock/sample.json').respond({
"people": [
{
"person": {
"firstName": "jim",
"lastName": "bob"
}
}
]
});
});
});
is the first part right, and how would I use jasmine to test the async promise?
Following on from my comment, here's how I would approach it.
describe('jsonLoader service', function() {
var uri;
beforeEach(module('example', function($provide) {
$provide.constant('TABLE_DATA_LOC', uri = 'mock/sample.json');
}));
it('should load JSON in a $timeout and return the response data', inject(function($httpBackend, $timeout, jsonLoader) {
var responseData = 'whatever', resolved = false;
$httpBackend.expectGET(uri).respond(responseData);
jsonLoader.load().then(function(data) {
expect(data).toBe(responseData);
resolved = true;
});
$timeout.flush();
$timeout.verifyNoPendingTasks();
expect(resolved).toBeFalsy();
$httpBackend.flush();
$httpBackend.verifyNoOutstandingRequest();
expect(resolved).toBeTruthy();
}));
});
Plunker demo ~ http://plnkr.co/edit/jmc9FWjbOkpmT6Lu8kVn?p=preview
Related
I'm writing a test to simply check that an endpoint is being called by a service. I'm using $httpBackend to mock this response, however the test is still hitting the real backend endpoint, and not using the $httpBackend mock.
Service
function getOptions() {
return $http.get('/apiv1/something').then(function (data) {
return [{
name: "eGo C"
image: "http://www.google.co.uk/logo.png"
}];
};
}
Test
describe('product service', function () {
var $httpBackend, service, $rootScope;
var failTest = function (error) {
expect(error).toBeUndefined();
};
beforeEach(module('appName'));
beforeEach(inject(function (_$httpBackend_, productService, _$rootScope_) {
$httpBackend = _$httpBackend_;
$rootScope = _$rootScope_;
service = productService;
}));
afterEach(function() {
$httpBackend.verifyNoOutstandingExpectation();
$httpBackend.verifyNoOutstandingRequest();
});
describe('getOptions', function () {
it('should make a call to the backend', function () {
var handler,
myThings,
errorStatus,
mockResponce;
mockResponce = [
{
name: 'evod kit',
img: 'img.jpg'
}
];
myThings = [];
errorStatus = '';
handler = {
success: function(data) {
console.log("success " + data);
myThings = data;
},
error: function(data) {
console.log("error " + data);
errorStatus = data;
}
};
spyOn(handler, 'success').and.callThrough();
spyOn(handler, 'error').and.callThrough()
$httpBackend.when('GET', '/apiv1/something').respond(mockResponce);
service.getOptions().then(handler.success, handler.error);
$httpBackend.flush();
console.log("MY THINGS ", myThings);
// PROBLEM!!! == FROM THE SERVICE -> [{name: "eGo C"
// image: "http://www.google.co.uk/logo.png"
// }]
});
});
try this instead :
let $rootScope
beforeEach(angular.mock.inject(function( _$httpBackend_, _$rootScope_){
$rootScope = _$rootScope_
$rootScope.httpbackend = _$httpBackend_
}
Essentially.. I'm an idiot. In the service I was returning a hardcoded array as the backend is yet to be built. $httpBackend was doing its job and returning the new data, but of course, it was not getting returned by the service. Sigh. I need a beer.
I am trying to write the test cass for the factory which is returing a JSON response.
But I am getting the error:
Error: [$injector:unpr] http://errors.angularjs.org/1.4.1/$injector/unpr?p0=serviceProvider%20%3C-%20service
at Error (native)
Here is my code:
(function () {
angular.module('uspDeviceService',[]).factory('getDevice', GetDevice);
GetDevice.$inject = ['$http'];
function GetDevice($http) {
getDeviceList = function() {
return $http.get("static/test-json/devices/device-list.json");
}
return {
getDeviceList: getDeviceList
}
}
}());
Code for Test case:
describe('Get Product test', function() {
beforeEach(module('uspDeviceService'));
var service, httpBackend, getDevice ;
beforeEach(function () {
angular.mock.inject(function ($injector) {
//Injecting $http dependencies
httpBackend = $injector.get('$httpBackend');
service = $injector.get('service');
getDevice = $injector.get('getDevice');
})
});
console.log('Injection Dependencies is done');
describe('get Device List', function () {
it("should return a list of devices", inject(function () {
httpBackend.expectGET("static/test-json/devices/device-list.json").respond("Response found!");
httpBackend.flush();
}))
})
});
I am new to Angular Unit testing, can anyone please help me, where I am going wrong..
Two things that jump out at me:
Your angular.module declaration is defining a module, not getting the module. I would encourage you to split that up so that it's a fair bit more clear what your intent is.
angular.module('uspDeviceService', []);
angular.module('uspDeviceService').factory('getDevice', GetDevice);
It likely works as-is, but clarity is important.
What is...service? It's not defined anywhere in your code, and Angular can't find it either, hence the error message. You may be looking to get getDevice instead. Also, name your test variable with respect to what it actually is, so you don't confuse yourself.
// defined above
var getDevice;
// while injecting
getDevice = $injector.get('getDevice');
Supposing that you have an angularjs controller myController defined in myModule. The controller do some action when the api call is success and shows a flash message when api returns success = false. The your controller code would be something like
angular.module('myModule')
.controller( 'myController', function ( $scope,flashService, Api ) {
Api.get_list().$promise.then(function(data){
if(data.success) {
$scope.data = data.response
}
else{
flashService.createFlash(data.message, "danger");
}
});
});
Now to test both success = true and success = false we
describe('myController', function(){
var $rootScope, $httpBackend, controller, flashService;
var apilink = 'http://apilink';
beforeEach(module('myModule'));
beforeEach(inject(function(_$httpBackend_,_$rootScope_, _$controller_, _flashService_) {
$rootScope = _$rootScope_;
$httpBackend = _$httpBackend_;
flashService = _flashService_;
controller = _$controller_("myController", {$scope: $rootScope});
}));
it('init $scope.data when success = true', function(){
$httpBackend.whenGET(apilink)
.respond(
{
success: true,
response: {}
});
$httpBackend.flush();
expect($rootScope.data).toBeDefined();
});
it('show flash when api request failure', function(){
spyOn(flashService, 'createFlash');
$httpBackend.whenGET(apilink)
.respond(
{
success: false
});
$httpBackend.flush();
expect(flashService.createFlash).toHaveBeenCalled();
});
});
You are always going to mock the response because here we are testing the javascript code behaviour and we are not concerned with the Api. You can see when success the data is initialized and when success is false createFlash is called.
As far as test for factory is concerned you can do
describe('Get Product test', function() {
beforeEach(module('uspDeviceService'));
var service, httpBackend, getDevice ;
beforeEach(function () {
inject(function ($injector) {
httpBackend = $injector.get('$httpBackend');
service = $injector.get('service');
getDevice = $injector.get('getDevice');
});
});
describe('get Device List', function () {
it("should return a list of devices", inject(function () {
httpBackend.expectGET("static/test-json/devices/device- list.json").respond("Response found!");
var result = getDevice.getDeviceList();
httpBackend.flush();
expect(result).toEqual('Response found!');
}));
});
});
I'm attempted to unit test a service. I've injected the service however the method call getAllProducts() doesn't appear to run however the test still passes!
Plnkr
service.js
angular.module('vsApp')
.factory('productsDataService', function($http) {
var service = {
getAllProducts: getAllProducts
};
// get all products
function getAllProducts() {
return $http.get('/apiv1/getAllProducts/').then(function(data) {
return (data);
});
}
return service;
});
spec.js
// jasmine
describe('products data service', function () {
var $httpBackend, productsDataService;
beforeEach(module('vsApp'));
beforeEach(inject(function(_$httpBackend_, _productsDataService_) {
$httpBackend = _$httpBackend_;
productsDataService = _productsDataService_;
}));
it('should get all products', inject(function() {
console.info("get all");
// mock response for the http call in the service
$httpBackend.when('GET', '/apiv1/getAllProducts/')
.respond({name: 'item', price: '932'});
//this doesn't seem to run??
productsDataService.getAllProducts().then(function(response) {
expect(response.data.length).toBeGreaterThan(1);
});
}));
});
Ok, you have to make it sync. (all pending request will get resolved) using $http.flush();
Working demo as expected
productsDataService.getAllProducts().then(function(response) {
console.log(response);
expect(response.data.length).toBeGreaterThan(999);
});
$httpBackend.flush(); // <=============== here.
var app= angular.module('app', []);
below is my factory method that will get the data from the sample.json
app.factory('factoryGetJSONFile', function($http) {
return {
getMyData: function(done) {
$http.get('mock/sample.json')
.success(function(data) {
done(data);
})
.error(function(error) {
alert('An error occured whilst trying to retrieve your data');
});
}
}
});
below is my controller. I can able to access the service data in my controller
app.controller('homeController', ['$scope', 'factoryGetJSONFile', function ($scope, factoryGetJSONFile) {
factoryGetJSONFile.getMyData(function (data) {
$scope.name = data.projectDetails.name;
$scope.duration = data.projectDetails.duration;
console.log($scope.name+ " and duration is " + $scope.duration);
});
}]);
Below is my sample.json
{
"status": 200,
"projectDetails": {
"name": "Project Name",
"duration": "4 Months",
"business_place": "Dummy address"
}
}
How to write the unit test cases for the above get service. I would like to test projectDetails.name in my test cases.
To mock http responses you can use the $httpbackend service. For example, if you want to test the getMyData method of the object created by your factory, you'd do something like:
var $httpbackend,
factoryGetJSONFile;
var sample.json = // Your sample JSON
var getUrl = 'mock/sample.json';
beforeEach(inject(function(_$httpbackend_, _factoryGetJSONFile_) {
// Load app
module('app');
// Make services available in tests
$httpbackend = _$httpbackend_;
factoryGetJSONFile = _factoryGetJSONFile;
// Mock the server's response
$httpbackend.when('GET', getUrl).
respond(sample.json);
}));
It('factoryGetJSONFile.getMyData should make correct GET request', function() {
// Call your services method and flush $httpbackend so the mock response is sent
var response = factoryGetJSONFile.getMyData();
$httpBackend.flush();
expect(response).toEqual(jasmine.objectContaining(sample.json));
});
I have seen some questions regarding this but all of them was specific to each case and I couldn't find a solution for my case in those posts.
I have a current controller:
function Login(authService, $scope) {
var vm = this;
vm.submit = submit;
vm.form = {};
function submit() {
if ($scope.loginForm.$invalid) {
vm.invalid = true;
return;
} else {
var data = {
usr: vm.form.email,
pwd: vm.form.password,
vendorId: 99
};
authService.login(data).then(success, error);
}
}
function success(res) {
if (res.data) {
//Do stuff
}
}
function error(error) {
console.log("Error ", error);
}
}
And the following unit test:
describe('Login', function() {
beforeEach(module('app'));
var loginCtrl, scope, $httpBackend, authService;
var loginResponse = [{
"data": {
"avatar": "avatar",
"gender": "M",
"hid": "hid,
"id": "id",
"role": "Adult",
"token": "token"
}
}];
var loginRequest = { "usr": "test#teste.com", "pwd": "123teste!", "vendorId": 99 };
beforeEach(inject(function($rootScope, _$httpBackend_, $controller, _authService_) {
$httpBackend = _$httpBackend_;
scope = $rootScope.$new();
loginCtrl = $controller('Login', {
$scope: scope
});
authService = _authService_;
}));
afterEach(function() {
$httpBackend.verifyNoOutstandingExpectation();
$httpBackend.verifyNoOutstandingRequest();
});
describe("submit", function() {
it("should send login data to the server", function() {
// expect(loginCtrl.login).toBe(false);
//Tells the $httpBackend service to expect a POST call to be made to a service and that it will return
//loginResponse object that was defined before
$httpBackend.expectPOST('api/current/api/login').respond(loginResponse);
//Execution of the service
var deferred = authService.login(loginRequest);
var users;
deferred.then(function(response){
users = response.data;
});
// expect(loginCtrl.login).toBe(true);
//Preserve the asynchronous nature of the call while at the same time be able to test the response of the call
$httpBackend.flush();
// dump(users);
expect(users).toEqual(loginResponse);
// expect(loginCtrl.login).toBe(true);
});
});
});
And I am getting the error:
Error: Unexpected request: GET signup/signup.html
No more request expected
I have found why this error occurs (I think). I'm using ui-router and it seems that it is always trying to do a GET request for the router root:
$urlRouterProvider.otherwise('/signup/');
$stateProvider
/* NOT AUTHENTICATED STATES */
.state('signup', {
url: '/signup/',
templateUrl: 'signup/signup.html',
controller: 'Signup as signupCtrl',
data: {
authorizedRoles: [AUTH_EVENTS.notAuthenticated]
}
})
Now I have no idea why, or how to fix it... Can someone understand what I'm doing wrong?
Thanks in advance!
Edit: authService
function authService($http, Session) {
var service = {
login : login
};
return service;
function login(credentials) {
console.log('authservice:', credentials);
return $http.post('api/current/api/login', credentials).then(function(res){
if (res.data.data){
var user = res.data.data;
Session.create(user.id, user.hid, user.token, credentials.usr, user.role, user.gender);
}
return res.data;
});
}
}
The template is requested, but you didn't inform the $http mock that it would be.
Register your template with $httpBackend
$httpBackend.expect('GET', 'signup/signup.html');
https://docs.angularjs.org/api/ngMock/service/$httpBackend#expect
Updated your function and try this code should work. Another option is to add ur template in template cache using gulp if you are using gulp this template get call problem is very specific with ui router and i have seen bug posted against it in github.
$httpBackend.expectGET('signup/signup.html').respond(200);
$httpBackend.flush();
$httpBackend.expectPOST('api/current/api/login').respond(loginResponse);
//Execution of the service
var deferred = authService.login(loginRequest);
var users;
deferred.then(function(response){
users = response.data;
});
// expect(loginCtrl.login).toBe(true);
//Preserve the asynchronous nature of the call while at the same time be able to test the response of the call
$httpBackend.flush();
// dump(users);
expect(users).toEqual(loginResponse);
// expect(loginCtrl.login).toBe(true);