'No more request expected' error when using $httpBackend - angularjs

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...
});
});

Related

Unsure on how to successfully test this function using $httBackend

This is the function in the controller:
var vm = this;
vm.getData = getData;
function getData(val) {
return $http.get('/get-data', {
params: {
query: val
}
}).then(function(response) {
return response.data;
});
}
and this is my (stripped down) test file:
describe('Controller: MyCtrl', function() {
'use strict';
var MyCtrl;
var rootScope;
var scope;
var httpMock;
beforeEach(function() {
module('MyModule');
inject(function($controller, $rootScope, $httpBackend) {
rootScope = $rootScope;
scope = $rootScope.$new();
httpMock = $httpBackend;
MyCtrl = $controller('MyCtrl as vm', {
$rootScope: rootScope,
$scope: scope,
$http: httpMock,
});
});
});
describe('vm.getData()', function() {
it('returns the required data', function() {
httpMock.when('GET', '/get-data?query=test-val').respond(200, {data: 'test-data'});
httpMock.flush();
expect(scope.vm.getData('test-val')).toEqual('test-data');
});
});
});
I would like to test that the result if calling getData() return the correct data.
Currently I'm getting the error $http.get is not a function. Setting a breakpoint in my function shows that http is stubbed with $httpBackend though.
I think there is something fundamental I'm not grasping - any pointers would be greatly appreciated.
You shouldn't have to create the controller with:
$http: $httpBackend
to mock the backend. $httpBackend will mock the request itself already.
Further the test and assertion is done in the wrong order:
httpMock.when('GET', '/get-data?query=test-val').respond(200, {data: 'test-data'});
MyCtrl.getData('test-val').then(function(_result_){ //perform the request
result = _result_; //save the result of the promise
});
httpMock.flush(); //execute the request
expect(result).toBe('test-data'); //assert that the result is as expected

Jasmine unit test angular service

i begin to use Jasmine in unit testing angularjs and see a lot example but not work i have usersservice and i need to make unit test for it
Please i need work demo
(function () {
'use strict';
angular
.module('app')
.factory('UserService', UserService);
UserService.$inject = ['$http'];
function UserService($http) {
var service = {};
service.GetAll = GetAll;
return service;
function GetAll(page) {
return $http.get('https://api.github.com/users').then(handleSuccess, handleError('Error getting all users'));
}
// private functions
function handleSuccess(res) {
return res.data;
}
function handleError(error) {
return function () {
return { success: false, message: error };
};
}
}})();
Below is a test demo for your service:
describe("UserService", function() {
var service;
var $rootScope;
var $httpBackend;
var dataMock = ["John", "Albert", "Mary"];
beforeEach(module('app'));
beforeEach(inject(function($injector) {
$rootScope = $injector.get('$rootScope');
$httpBackend = $injector.get('$httpBackend');
service = $injector.get('UserService');
}));
afterEach(function() {
$httpBackend.verifyNoOutstandingExpectation();
$httpBackend.verifyNoOutstandingRequest();
});
it('should fetch 3 users', function() {
$httpBackend.when('GET', 'https://api.github.com/users').respond(dataMock);
service.GetAll().then(function(users) {
expect(users.length).toBe(3);
expect(users).toEqual(dataMock);
});
$httpBackend.flush();
});
it('should return error', function() {
$httpBackend.when('GET', 'https://api.github.com/users').respond(403);
service.GetAll().then(function(error) {
expect(error.success).toBe(false);
expect(error.message).toEqual('Error getting all users');
});
$httpBackend.flush();
});
});
You can see it working on Plunker: https://plnkr.co/edit/agV0rO?p=preview

Mocking $mdSideNav in unit test

I have a simple enough function that closes an $mdSidenav instance in my application
function closeSideNav() {
$mdSidenav('left').close();
}
I'm now needing to unit test this, but am having trouble writing an expectation for the close() call on $mdSidenav.
I thought about using $provide in my test spec
module(function($provide) {
$provide.value('$mdSidenav', function(id) {
return {
close: jasmine.createSpy('$mdSidenav.close')
}
})
});
beforeEach(inject(function(_$controller_, _$mdSidenav_) {
$controller = _$controller_;
$mdSidenav = _$mdSidenav_;
}));
beforeEach(function() {
vm = $controller('NavbarController', {
$mdSidenav: $mdSidenav
});
});
describe('vm.closeSideNav', function() {
beforeEach(function() {
spyOn($mdSidenav, 'close');
vm.closeSideNav()
});
it('should call $mdSidenav.close()', function() {
expect($mdSidenav.close).toHaveBeenCalled();
});
});
This throws a couple of errors:
Error: close() method does not exist
Error: Expected a spy, but got undefined.
Has anyone managed to mock out $mdSidenav and offer me some guidance please?
Thanks
UPDATE
Based on the suggested answer, I have now updated my test spec to
'use strict';
describe('NavbarController', function() {
var $controller,
vm,
$mdSidenav,
sideNavCloseMock;
beforeEach(function() {
module('app.layout');
sideNavCloseMock = jasmine.createSpy();
module(function($provide) {
$provide.value('$mdSidenav', function() {
return function(sideNavId) {
return {close: sideNavCloseMock}
}
})
});
});
beforeEach(inject(function(_$controller_, _$mdSidenav_) {
$controller = _$controller_;
$mdSidenav = _$mdSidenav_;
}));
beforeEach(function() {
vm = $controller('NavbarController', {
$mdSidenav: $mdSidenav
});
});
describe('vm.closeSideNav', function() {
beforeEach(function() {
vm.closeSideNav()
});
it('should call $mdSidenav.close()', function() {
expect(sideNavCloseMock).toHaveBeenCalled();
});
});
});
And for a sanity check, my actual controller looks as follows:
(function () {
'use strict';
angular
.module('app.layout')
.controller('NavbarController', Controller);
Controller.$inject = ['$mdSidenav'];
function Controller($mdSidenav) {
var vm = this;
vm.closeSideNav = closeSideNav;
//This only affects the sideNav when its not locked into position, so only on small\medium screens
function closeSideNav() {
$mdSidenav('left').close();
}
}
})();
Unfortunately this still isn't working for me, and I end up with a different error
TypeError: undefined is not a constructor (evaluating '$mdSidenav('left').close())
close method doesn't belong to $mdSidenav. $mdSidenav is a function that returns a side nav object. That's why it complains 'close() method does not exist'.
What you can do is mock the $mdSidenav to return an object hat has mocked close method, like this: -
var sideNavCloseMock;
beforeEach(module(function($provide){
sideNavCloseMock = jasmine.createSpy();
$provide.factory('$mdSidenav', function() {
return function(sideNavId){
return {close: sideNavCloseMock};
};
});
}));
then do
it('should call $mdSidenav.close()', function() {
expect(sideNavCloseMock).toHaveBeenCalled();
});

AngularJS Testing and $http

So Im trying to figure out how to write unit tests for my angular controller. I am using karma as my runner. I was able to write 1 successful test but every time I try to write another test it yells at me about unexpected calls and such.
Here is my controller im trying to test.
(function (angular) {
'use strict';
var ngModule = angular.module('myApp.dashboardCtrl', []);
ngModule.controller('dashboardCtrl', function ($scope, $http) {
//"Global Variables"
var vm = this;
vm.success = false;
vm.repos = [];
//"Global Functions"
vm.addRepository = addRepository;
vm.listRepos = listRepos;
//Anything that needs to be instantiated on page load goes in the init
function init() {
listRepos();
}
init();
// Add a repository
function addRepository(repoUrl) {
$http.post("/api/repo/" + encodeURIComponent(repoUrl)).then(function (){
vm.success = true;
vm.addedRepo = vm.repoUrl;
vm.repoUrl = '';
listRepos();
});
}
//Lists all repos
function listRepos() {
$http.get('/api/repo').then( function (response){
vm.repos = response.data;
});
}
});
}(window.angular));
So I have a test written for listRepos(). It goes as follows
describe('dashboardCtrl', function() {
var scope, httpBackend, createController;
// Set up the module
beforeEach(module('myApp'));
beforeEach(inject(function($rootScope, $httpBackend, $controller) {
httpBackend = $httpBackend;
scope = $rootScope.$new();
createController = function() {
return $controller('dashboardCtrl', {
'$scope': scope
});
};
}));
afterEach(function() {
httpBackend.verifyNoOutstandingExpectation();
httpBackend.verifyNoOutstandingRequest();
});
it('should call listRepos and return all repos from the database', function() {
var controller = createController();
var expectedResponse = [{id: 12345, url: "https://github.com/myuser/myrepo.git"}];
httpBackend.expect('GET', '/api/repo')
.respond(expectedResponse);
httpBackend.flush();
scope.$apply(function() {
scope.listRepos;
});
expect(controller.repos).toEqual(expectedResponse);
});
This works and the test passes. Now my problem is I want to write another test to test the other function that calls a new api endpoint.
This is the test im trying to write for addRepository.
it('should addRepository to the database', function() {
var controller = createController();
var givenURL = "https://github.com/myuser/myURLtoMyRepo.git";
httpBackend.expect('POST', '/api/repo/' + encodeURIComponent(givenURL)).respond('success');
httpBackend.flush();
scope.$apply(function() {
scope.addRepository(givenURL);
});
expect(controller.success).toBe(true);
expect(controller.listRepos).toHaveBeenCalled();
});
The error I get when I add this test to the spec is:
Error: Unexpected request: GET /api/repo
Expected POST /api/repo/https%3A%2F%2Fgithub.com%2Fmyuser%2FmyURLtoMyRepo.git
at $httpBackend
Error: [$rootScope:inprog] $digest already in progress
http://errors.angularjs.org/1.4.8/$rootScope/inprog?p0=%24digest
The example I am working with is this one here
Any suggestions or tips is greatly appreciated!
UPDATE:
So changed my function to return the promise from the $http.post,
I rewrote my 2nd test and also wrapped my first test in a describe block describing the function its trying to test.
With the following:
describe('addRepository', function () {
it('should addRepository to the database', function () {
var controller = createController();
var givenURL = "https://github.com/myuser/myURLtoMyRepo.git";
httpBackend.expect('POST', '/api/repo/' + encodeURIComponent(givenURL)).respond('success');
scope.$apply(function () {
scope.addRepository(givenURL);
});
httpBackend.flush();
expect(controller.success).toBe(true);
});
it('should call listRepos', function() {
var controller = createController();
httpBackend.expect('GET', '/api/repo').respond('success');
controller.controller().then(function (result) {
expect(controller.listRepos).toHaveBeenCalled();
});
httpBackend.flush();
});
});
I still get the error:
Error: Unexpected request: GET /api/repo
Expected POST /api/repo/https%3A%2F%2Fgithub.com%2Fmyuser%2FmyURLtoMyRepo.git
at $httpBackend
Error: [$rootScope:inprog] $digest already in progress
but also
TypeError: 'undefined' is not a function (evaluating 'controller.controller()')
Error: Unflushed requests: 1
which shows 2 tests failed.
The flush should come after the call to the function. I'd also change the function to return the promise from the $http.post:
// Add a repository
function addRepository(repoUrl) {
return $http.post("/api/repo/" + encodeURIComponent(repoUrl)).then(function (){
vm.success = true;
vm.addedRepo = vm.repoUrl;
vm.repoUrl = '';
listRepos();
});
}
And then in the test you can call it and test the success part:
EDIT
I changed the controller.controller() to what you have.
it('should call listRepos', function() {
// Your setup
ctrl.addRepository().then(function(result) {
expect(ctrl.listRepos).toHaveBeenCalled();
});
});
EDIT 2
I emulated as best i could your code and the tests I write for the code:
(function () {
'use strict';
angular
.module('myApp')
.controller('DashboardController',DashboardController);
DashboardController.$inject = ['$http'];
function DashboardController($http) {
var vm = this;
vm.success = false;
vm.repos = [];
vm.addRepository = addRepository;
vm.listRepos = listRepos;
init();
// Anything that needs to be instantiated on page load goes in the init
function init() {
vm.listRepos();
}
// Add a repository
function addRepository(repoUrl) {
return $http.post('http://jsonplaceholder.typicode.com/posts/1.json').then(function (){
vm.success = true;
vm.addedRepo = vm.repoUrl;
vm.repoUrl = '';
vm.listRepos();
});
}
// Lists all repos
function listRepos() {
return $http.get('http://jsonplaceholder.typicode.com/posts/1').then( function (response){
vm.repos = response.data;
});
}
};
}());
Here I'm using an online JSONPlaceholder API to simulate HTTP calls as I, obviously, can't hit what you're pointing at. And for the test (which all pass):
(function() {
'use strict';
fdescribe('DashBoardController', function() {
var $rootScope, scope, ctrl, $httpBackend;
beforeEach(module('myApp'));
beforeEach(inject(function(_$rootScope_, _$httpBackend_,$controller) {
$rootScope = _$rootScope_;
scope = $rootScope.$new();
$httpBackend =_$httpBackend_;
ctrl = $controller('DashBoardController',{
$scope: scope
});
}));
beforeEach(function() {
// Setup spies
spyOn(ctrl,'listRepos');
});
describe('controller', function() {
it('should be defined', function() {
expect(ctrl).toBeDefined();
});
it('should initialize variables', function() {
expect(ctrl.success).toBe(false);
expect(ctrl.repos.length).toBe(0);
});
});
describe('init', function() {
it('should call listRepos', function() {
$httpBackend.expectGET('http://jsonplaceholder.typicode.com/posts/1')
.respond({success: '202'});
$httpBackend.expectPOST('http://jsonplaceholder.typicode.com/posts/1.json')
.respond({success: '202'});
ctrl.addRepository().then(function(result) {
expect(ctrl.success).toBe(true);
expect(ctrl.repoUrl).toBe('');
expect(ctrl.listRepos).toHaveBeenCalled();
});
$httpBackend.flush();
});
});
});
}());

Provider mock error: Expected a spy, but got Function

I'm very new to jasmine and I got some issue trying to mock a provider.
I have a provider that looks like :
angular.module('myApp')
.factory('MyService', function ($resource) {
return {
actionResource : function(projectId, actionId){
var url = 'blablabla';
if(actionId){
url += "/"+actionId;
}
return $resource(url, {}, {
'create': {
method: 'POST'
}
});
},
};
});
I have a controller using this factory
angular.module('myApp')
.controller('myCtrl', function ($scope, MyService, $state) {
$scope.addAction = function(){
MyService.actionResource($scope.projectId).create($scope.action,
function(){
$state.go('somewhere', {});
});
};
});
and I'd really like to test this controller, at the moment I'm doing something like :
'use strict';
describe('[test] [controller] - myCtrl', function() {
beforeEach(angular.mock.module('myApp'));
//mock creation
beforeEach(
module(function($provide) {
$provide.factory('MyService', function() {
var actionResource = function(projectId, actionId){
var create = function(){return {}};
return {create:create};
}
return {actionResource:actionResource}
}
)})
);
var $httpBackend, $rootScope, myController, mockMyService;
var scope = {};
beforeEach(angular.mock.inject(function($injector, $rootScope, MyService) {
var $controller = $injector.get('$controller');
scope=$rootScope.$new();
mockMyService = MyService;
spyOn(mockPilotageService, 'actionResource').and.callThrough();
spyOn(mockPilotageService.actionResource(), 'create');
createController = function() {
return $controller("myCtrl", {$scope:scope, myService:mockMyService});
};
}));
it('myCtrl - addAction() calls actionRessource.create() method', function() {
createController();
scope.addAction();
expect(mockMyService).toBeDefined();
expect(mockMyService.actionResource).toHaveBeenCalled();
expect(mockMyService.actionResource().create).toHaveBeenCalled();
});
});
and I'm getting this error :
Error: Expected a spy, but got Function.
at /Users/*****/myController.spec.js:86
So it's able to spy on mockMyService.actionResource but not on mockMyService.actionResource().create. I can't understand why
any help would be more than welcome
thanks

Resources