I'm new to AngularJS and running into some problems with unit testing. I've seen countless examples of mocking $httpBackend calls, but when I do that it won't work unless I also include $rootScope.$apply().
My service:
angular.module('myApp.services', ['ngResource'])
.factory('TestingService', ['$resource', function($resource) {
return $resource('/api/v1/values', {}, {
getValues: {
method: 'GET'
}
});
}]);
My unit test:
describe('Testing services', function() {
beforeEach(module('myApp.services'));
afterEach(function() {
inject(function($httpBackend) {
$httpBackend.verifyNoOutstandingExpectation();
$httpBackend.verifyNoOutstandingRequest();
});
});
describe('TestingService', function() {
it('would be nice to get an explanation for this',
inject(['$rootScope', '$httpBackend', 'TestingService',
function ($rootScope, $httpBackend, testingService) {
$httpBackend.expectGET('/api/v1/values').respond(100);
var result = testingService.getValues();
//$rootScope.$apply();
$httpBackend.flush();
expect(result).toBe(100);
alert(result);
}])
);
});
});
When Karma runs the test like this I get:
Error: No pending request to flush !
Error: Unsatisfied requests: GET /api/v1/values
And if I include the $rootScope.$apply(); I'll get this (and the alert of course also prints out a $promise):
Expected { $promise : { then : Function, catch : Function, finally : Function }, $resolved : true } to be 100.
Can anyone explain why I need "$rootScope.$apply();" to pass the expectGET?
And why the response is a promise instead of the mock response I've specified?
Found the problem after some sleep. Simple one fortunately.
I'm using Angular version 1.3.0-beta.2, but had an older version for angular-mocks. Updating the versions removes the need for "$root.$apply();".
The updated working test:
describe('Testing services', function() {
beforeEach(function(){
module('myApp.services');
this.addMatchers({
toEqualData: function(expected) {
return angular.equals(this.actual, expected);
}
});
});
afterEach(function() {
inject(function($httpBackend) {
$httpBackend.verifyNoOutstandingExpectation();
$httpBackend.verifyNoOutstandingRequest();
});
});
describe('TestingService', function() {
it('should work',
inject(['$rootScope', '$httpBackend', 'TestingService',
function ($rootScope, $httpBackend, testingService) {
$httpBackend.expectGET('/api/v1/values').respond( { key: 'value' } );
var result = testingService.getValues();
$httpBackend.flush();
expect(result).toEqualData( { key: 'value' } );
alert(angular.toJson(result, true));
}])
);
});
});
Related
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 trying to test a simple call to my API, and I'm going round in circles trying to work out why it's failing.
I've simplified things a bit.
This would be the error for the test below:
Error: Unexpected request: GET /api/search?blah=something
No more request expected
Here is the test:
it('does what it should', function() {
httpBackend.expectGET('/api/search?blah=something').respond(aTestResponse);
scope.search();
httpBackend.flush();
// expectations here...
});
The search function in the controller:
function search() {
myDataService.getSearchResults().query(mySearchParams, function(response) {
// do stuff
}
}
and the data service function:
function getSearchResults() {
return $resource('/api/search', {
param1: '#param1',
param2: '#param2',
...etc
});
}
Any suggestions would be really appreciated.
Edit - here is an edited, but more complete version of my spec file:
'use strict';
describe('Controller: BlahCtrl', function() {
beforeEach(module('blahApp'));
beforeEach(module(function($urlRouterProvider) {
$urlRouterProvider.deferIntercept();
}));
var BlahCtrl;
var scope;
var rootScope;
var httpBackend;
beforeEach(inject(function($controller, $rootScope, $httpBackend) {
httpBackend = $httpBackend;
scope = $rootScope.$new();
rootScope = $rootScope;
BlahCtrl = $controller('BlahCtrl as vm', {
$scope: scope
});
this.testResults = [
{
testProperty1: 'test-value-1-1',
testProperty2: 'test-value-1-2',
testProperty3: 'test-value-1-3'
},
{
testProperty1: 'test-value-2-1',
testProperty2: 'test-value-2-2',
testProperty3: 'test-value-2-3'
}
];
}));
beforeEach(function() {
this.addMatchers({
toEqualData: function(expected) {
return angular.equals(this.actual, expected);
}
});
});
it('stores the search results', function() {
httpBackend.expectGET('/api/search?blah=something').respond(this.testResults);
scope.vm.doSearch();
httpBackend.flush();
// expectations here...
});
});
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.
I am developing Chrome App with AngularJS. As my app uses chrome.storage I wrote wrapper:
angular.module('myApp').factory('chromeStorageApi', function($window){
if(typeof $window.chrome.storage == 'undefined')
return false;
return{
set:function(obj, callback){
$window.chrome.storage.local.set(obj, callback)
return true;
},
.............................
}
}
I have violated TDD methodology and now I want to test my wrapper. But all my attempts were not successful. I tried to check that, for example, $window.chrome.storage.local.set() has the same arguments as chromeStorageApi.set() but I could not find a way how I can mock $window.chrome.storage.local.
UPDATED:
My last version of Unit test:
describe('chromeStorageApi', function(){
beforeEach(module('myApp'));
it('should be able to set data to the storage', inject(function(chromeStorageApi, $window){
spyOn($window.chrome.storage.local, 'set')
chromeStorageApi.set({'key':'value'}, function(){ }());
expect($window.chrome.storage.local.set).toHaveBeenCalled();
expect($window.chrome.storage.local.set).toHaveBeenCalledWith({'key':'value'}, function(){ }());
}));
});
But I get an error:
TypeError: 'undefined' is not an object (evaluating '$window.chrome.storage')
I made working tests for me. Here there are:
describe('chromeStorageApi', function(){
var mockWindow, chromeStorageApi;
beforeEach(module('myApp'));
beforeEach(function(){
mockWindow = {
chrome:{
storage:{
local: sinon.stub({
set: function(){ },
get: function(){ },
remove: function(){ },
clear: function(){ }
})
}
},
addEventListener: function(){ }
}
module(function($provide){
$provide.value('$window', mockWindow);
})
})
beforeEach(inject(function(_chromeStorageApi_){
chromeStorageApi =_chromeStorageApi_;
}))
it('should be able to set data to the storage', function(){
chromeStorageApi.set({'key':'value'}, function(){ }());
expect(mockWindow.chrome.storage.local.set).toHaveBeenCalled();
expect(mockWindow.chrome.storage.local.set).toHaveBeenCalledWith({'key':'value'}, function(){ }());
});
it('should be able to get data from the storage', function(){
chromeStorageApi.get('key', function(){ });
expect(mockWindow.chrome.storage.local.get).toHaveBeenCalled();
expect(mockWindow.chrome.storage.local.get).toHaveBeenCalledWith('key');
})
})
I am using sinonJS to create stub with methods. I hope it will be helpful for someone.
I've defined the following service in my angular app :
services.factory('MyService', ['Restangular', function (Restangular) {
return {
events : { loading : true },
retrieveQuotes : function() {
return Restangular.all('quotes').getList().then(function() {
return { hello: 'World' };
});
}
};
}]);
and I'm writing the following spec to test it :
describe("MyService", function () {
beforeEach(module('MyApp'));
beforeEach(module("restangular"));
var $httpBackend, Restangular, ms;
beforeEach(inject(function (_$httpBackend_, _Restangular_, MyService) {
ms = MyService;
$httpBackend = _$httpBackend_;
Restangular = _Restangular_;
}));
it("retrieveQuotes should be defined", function () {
expect(ms.retrieveQuotes).toBeDefined();
});
it("retrieveQuotes should return array of quotes", function () {
$httpBackend.whenGET("internalapi/quotes").respond({ hello: 'World' });
ms.retrieveQuotes();
$httpBackend.flush();
});
});
Whenever I run the tests, the first test passes but the second test produces the error :
Error: Unexpected request: GET /internalapi/quotes
What am I doing wrong?
EDIT:
It turned out I'd configured Restangular like so ... RestangularProvider.setBaseUrl("/internalapi");. But I was faking calls to internalapi/quotes. Notice the lack of the "/". Once I added the slash /internalapi/quotes all was good :)
You need to tell $httpBackend to expect a GET request.
describe("MyService", function () {
beforeEach(module('MyApp'));
beforeEach(module("restangular"));
var Restangular, ms;
beforeEach(inject(function (_Restangular_, MyService) {
ms = MyService;
Restangular = _Restangular_;
}));
it("retrieveQuotes should be defined", function () {
expect(ms.retrieveQuotes).toBeDefined();
});
it("retrieveQuotes should return array of quotes", inject(function ($httpBackend) {
$httpBackend.whenGET("internalapi/quotes").respond({ hello: 'World' });
//expect a get request to "internalapi/quotes"
$httpBackend.expectGET("internalapi/quotes");
ms.retrieveQuotes();
$httpBackend.flush();
}));
});
Alternatively you can put your respond() on your expectGET(). I prefer to put my whenGET() statements in a beforeEach() that way I do not have to define the response within every test.
//expect a get request to "internalapi/quotes"
$httpBackend.expectGET("internalapi/quotes").respond({ hello: 'World' });
ms.retrieveQuotes();
$httpBackend.flush();
I had the same problem as you guys. My solution was to add a '/' at the start of the URL-parameter of the .expectGET. Using your example:
$httpBackend.expectGET("/internalapi/quotes").respond({ hello: 'world'})
Best of luck