AngularJS $httpBackend verify no interactions - angularjs

I'm looking for a way to evaluate $httpBackend to see if there are any interactions. I want to make sure it has never been called at this point in my test case. I've checked the documentation here: https://docs.angularjs.org/api/ngMock/service/$httpBackend and haven't found the answer.
Class using $http
class HomeService {
/*#ngInject*/
constructor ($http, $log) {
this.http = $http;
this.log = $log;
}
submit(keycode) {
this.log.log("submitting key code: " + keycode);
if (keycode === "") {
return false;
}
this.http.post(`/api/keycode/${keycode}`).then ( (response) => {
this.log.log(response);
return true;
});
}
}
export default HomeService;
test case so far.
import HomeService from './home.service';
describe('HomeService', () => {
let homeService, $httpBackend;
beforeEach(inject(($injector) => {
$httpBackend = $injector.get('$httpBackend');
}));
afterEach(function() {
$httpBackend.verifyNoOutstandingExpectation();
$httpBackend.verifyNoOutstandingRequest();
});
describe('submit', () => {
it('submit empty keycode', () => {
homeService = new HomeService($httpBackend, console);
let value = homeService.submit("");
expect(value).to.be.false;
//valid no interactions with $httpBackend here!
});
});
});

Even though
afterEach(function() {
$httpBackend.verifyNoOutstandingExpectation();
$httpBackend.verifyNoOutstandingRequest();
});
may be enough to throw on unwanted requests, to tie an error to specific spec use:
expect($httpBackend.verifyNoOutstandingExpectation).not.toThrow();

Related

How to unit test condition in promise then() karma and jasmine

I am using AngularJS 1.7 with Karma and Jasmine. And I have started learning Unit Test cases.
I have a sample method below in my controller
_this.method = function () {
Service.getData().then(function (response) {
if (response.productId === "ClientAPI") {
// Some code
}
else {
// Some Code
}
}, function (error) {
_this.inProgress = false;
if (error.status === 400) {
// Some Code
} else {
// Some Code
}
})
}
Below is my test case :
describe('Some Route :: Controller => ', function () {
var $componentController;
var Service;
beforeEach(module('app'));
beforeEach(inject(function (_$componentController_, _Service_) {
Service = _Service_;
spyOn(Service, 'getData').and.callFake(function() {
var deferred = $q.defer();
var response = {};
response.productId = "ClientAPI";
deferred.resolve(result);
return deferred.promise;
});
ctrl = $componentController('controllerName', { Service: Service });
}));
it('Ctrl Method : should true', function () {
ctrl.method();
expect(Service.getData).toHaveBeenCalled();
Service.getData().then(function (response) {
expect(response.productId).toBe("ClientAPI")
})
});
});
But my branch coverage is not showing for this condition if (response.productId === "ClientAPI") {
Not sure what I am doing wrong while testing in a promise.
You need to call $scope.$apply() to trigger the call of the promise callbacks:
beforeEach(inject(function (_$componentController_, _Service_) {
Service = _Service_;
spyOn(Service, 'getData').and.returnValue($q.resolve({ productId: 'ClientAPI' }));
ctrl = $componentController('controllerName', { Service: Service });
}));
it('Ctrl Method : should true', inject(function($rootScope) {
ctrl.method();
expect(Service.getData).toHaveBeenCalled();
$rootScope.$apply();
// now test that the ctrl state has been changed as expected.
// testing that the service has returned ClientAPI is completely useless:
// the service is a mock, and you have told the mock to return that
// this should test the component, based on what you've told the service
// to return. It's not supposed to test the mock service.
// testing what the service returns tests jasmine, not your code.
});

Testing then and catch from a promise in angular

I would like to test my then and catch function from my $scope.customerinfo. The problem is i dont know how exactly.
var app = angular.module('shop', ['ngRoute','ngResource'])
.factory('Customerservice', function ($resource) {
return $resource('http://localhost:8080/Shop/:customer',{customer: "#customer"});
})
.controller('customerController', function ($scope,Customerservice) {
$scope.customerinfo = CustomerService.get({customer: "Mark"});
$scope.customerinfo.$promise.then(function(info) {
return info;
}).catch(function(errorResponse) {
throw errorResponse;
});
});
Im not done yet but this is my jasmine code
describe('Testing the customerinfo', function () {
var $scope;
var $q;
var deferred;
beforeEach(module('shop'));
beforeEach(inject(function($controller, _$rootScope_, _$q_) {
$q = _$q_;
$scope = _$rootScope_.$new();
deferred = _$q_.defer();
$controller('userController', {
$scope: $scope
});
}));
it('should reject promise', function () {
// I want to check if the catch option is working
});
});
So how exactly can i do this, or do i need to refactor the code?
The jasmine 'it' method takes a done parameter that you can call for async testing
it('Should reject', function(done) {
someAsyncFunction().catch(function(result) {
expect(result.status).toBe(401);
done();
});
});

Unit testing controller with injected service

What is the best way to go about unit testing the following controller? I'm having trouble properly injecting AuthService into my controller. I've seen so many different ways to do it and I'm not really sure what the best practice is - i.e. mocks vs spies?
I have a simple service like this:
angular.module('users')
.factory('AuthService', ['$http', '$window',
function($http, $window) {
var authService = {};
authService.login = function(creds) {
return $http.post('/auth', creds)
.then(function(res) {
$window.localStorage.exampleToken = res.data.returned_token;
return res;
});
};
authService.isLoggedIn = function() {
if($window.localStorage.exampleToken) {
return true;
} else {
return false;
}
};
authService.clear = function() {
delete $window.localStorage.exampleToken;
};
return authService;
}]);
My controller:
angular.module('users')
.controller('ExampleCtrl', ['AuthService',
function(AuthService) {
var vm = this;
vm.isLoggedIn = AuthService.isLoggedIn();
}]);
My unfinished test:
describe('ExampleCtrl', function() {
beforeEach(module('users'));
var ctrl;
beforeEach(inject(function($controller) {
ctrl = $controller('ExampleCtrl', {});
}));
describe('when logged in', function() {
beforeEach(function() {
// how do i mock the isLoggedIn function to
// return true
});
it('should return true', function() {
expect(ctrl.isLoggedIn).toBe(true);
});
});
describe('when not logged in', function() {
beforeEach(function() {
// how do i mock the isLoggedIn function to
// return false
});
it('should return false', function() {
expect(ctrl.isLoggedIn).toBe(false);
});
});
});
You can merely use the callFake function of Jasmine:
By chaining the spy with and.callFake, all calls to the spy will delegate to the supplied function.
var AuthService; //so that you can have a reference within all your test file
beforeEach(function() {
inject(function(_AuthService_) {
AuthService = _AuthService_;
});
spyOn(AuthService, 'isLoggedIn').and.callFake(function() {
return true;
});
});

spyOn with Promise

I have the following function that I would like to spy... but it contains a promise... But I am getting TypeError: 'undefined' is not an object (evaluating 'modalService.showModal({}, modalOptions).then')
Because of course I have just spyOn(modalService,'showModal')
How do I account for the promise too so ??
_modalService = {
close: function (value) { console.log(value) },
dismiss: function (value) { console.log(value) },
showModal: function (value) { console.log(value) }
};
spyOn(_modalService, 'close');
spyOn(_modalService, 'dismiss');
spyOn(_modalService, 'showModal');
Controller function:
user.resetPassword = function () {
var modalOptions = {
closeButtonText: 'Cancel',
actionButtonText: 'Reset',
headerText: 'Reset Password',
bodyText: 'Are you sure you want to reset the users password?'
};
modalService.showModal({}, modalOptions).then(function (result) {
if (result === 'ok') {
userDataService.resetPassword(user.data).then(function (result) {
$scope.$emit('showSuccessReset');
});
};
});
};
Here is my unit test:
it('should allow the users password to be reset', function () {
var controller = createController();
controller.resetPassword();
$httpBackend.flush();
})
*******************UPDATE
So I change it to this:
//Create a fake instance of the modal instance. TO ensure that the close is called
_modalService = {
close: function (value) { console.log(value) },
dismiss: function (value) { console.log(value) },
showModal: function (value) { console.log(value) }
};
spyOn(_modalService, 'close');
spyOn(_modalService, 'dismiss');
spyOn(_modalService, 'showModal').and.callThrough();
_modalService.showModal = function() {
var deferred = $q.defer();
deferred.resolve('Remote call result');
return deferred.promise;
};
To be honest though I am not sure I could explain this. While I understand all the async stuff... I am not sure how jasmine is using this to make it all work. Can anyone explain the flow???? Also I feel the syntax is wrong... how would you typically write this so it looks better/cleaner...??
When you need to mock a function that returns a promise, you have two options:
Return a mocked promise (an object that resembles a promise);
Return a real promise.
I suggest #2 because it's easier and you don't have to worry about replicating the whole promise API. In other words, it isn't worth mocking a promise itself.
Now about Jasmine: you only need to use spyOn when you already have an object (not a mock) and you want to spy on (no pun intended) one of its methods. In your case, your whole object is fake, so you could use jasmine.createSpyObj instead.
The following example should make all of the above clearer:
SUT
app.controller('MainCtrl', function($scope, modal, service) {
$scope.click = function() {
modal.show().then(function(result) {
if (result === 'ok') {
service.resetPassword();
}
});
};
});
Test
describe('Testing a controller', function() {
var $scope, $q,
ctrl, modalMock, serviceMock;
beforeEach(function() {
module('plunker');
modalMock = jasmine.createSpyObj('modal', ['show']);
serviceMock = jasmine.createSpyObj('service', ['resetPassword']);
inject(function($rootScope, $controller, _$q_) {
$scope = $rootScope.$new();
$q = _$q_;
ctrl = $controller('MainCtrl', {
$scope: $scope,
modal: modalMock,
service: serviceMock
});
});
});
it('should reset the password when the user confirms', function() {
// Arrange
var deferred = $q.defer();
deferred.resolve('ok');
modalMock.show.and.returnValue(deferred.promise);
// Act
$scope.click();
$scope.$digest(); // Makes Angular resolve the promise
// Assert
expect(serviceMock.resetPassword).toHaveBeenCalled();
});
it('should not reset the password when the user cancels', function() {
// Arrange
var deferred = $q.defer();
deferred.resolve('cancel');
modalMock.show.and.returnValue(deferred.promise);
// Act
$scope.click();
$scope.$digest(); // Makes Angular resolve the promise
// Assert
expect(serviceMock.resetPassword).not.toHaveBeenCalled();
});
});
Working Plunker
That mock arrangement code within each test could be moved into a beforeEach section so it doesn't get duplicated. I didn't do that in order to make things simple.

Override $exceptionHandler behaviour in service unit test

I have a service which throws an error, and I'd like to unit test that, but somehow I cannot get it overwritten, which I did manage to do for controllers:
describe('some test', function () {
var myService, exceptionHandler;
beforeEach(angular.mock.module('myModule', function($exceptionHandlerProvider) {
$exceptionHandlerProvider.mode("log");
}));
beforeEach(inject(function ($injector, $exceptionHandler) {
exceptionHandler = $exceptionHandler;
myService = $injector.get('myService');
}));
it('should throw an error', function () {
theService.somefunction('create an error');
expect(exceptionHandler.errors.length).toBe(1);
expect(exceptionHandler.errors[0].message).toBe('some text');
});
});
});
in somefunction I just do this:
throw new Error('some text')
The problem is that the error is just logged to the console in Karma, so it seems to be rethrown rather then logged.
How can I fix this?
It's advised to use sinon when stubbing an object (will show both ways though):
describe('some test', function () {
var myService, exceptionHandler = {};
exceptionHandler.fn = function(exception, cause) {
throw exception;
};
beforeEach(module('myModule', function($provide) {
$provide.factory('$exceptionHandler', function(){
return function(exception, cause){
return exceptionHandler.fn(exception, cause);
};
});
}));
beforeEach(inject(function ($injector) {
myService = $injector.get('myService');
}));
it('should throw an error', function () {
var old = exceptionHandler.fn, caught = false, message = '';
exceptionHandler.fn = function(exception, cause) {
caught = true;
message = exception.message;
};
myService.somefunction('create an error');
expect(caught).toBe(true);
expect(message).toBe('message');
exceptionHandler.fn = old; //restore
// using sinon.js
sinon.stub(exceptionHandler, 'fn', function(exception){ });
myService.somefunction('create an error');
expect(exceptionHandler.fn.called).toBe(true);
expect(exceptionHandler.fn.getCall(0).args[0]).toMatch(/message/);
exceptionHandler.fn.restore();
});
});
sinon makes it easier to stub stuff, and check the call count and restore functionality after you're done

Resources