I am new to TDD and am testing an authInterceptor (I have chai/mocha/sinon available to me) in angular js, which has two functions, a request, and a responseError. I successfully tested the request function, but I don't know how (scoured the docs) to mock a 401 (unauthorized) error. Here is the interceptor:
export default function AuthInterceptor($q, $injector, $log) {
'ngInject';
return {
request(config) {
let AuthService = $injector.get('AuthService');
if (!config.bypassAuthorizationHeader) {
if (AuthService.jwtToken) {
config.headers.Authorization = `Bearer ${AuthService.jwtToken}`;
} else {
$log.warn('Missing JWT', config);
}
}
return config || $q.when(config);
},
responseError(rejection) {
let AuthService = $injector.get('AuthService');
if (rejection.status === 401) {
AuthService.backToAuth();
}
return $q.reject(rejection);
}
};
}
Here are my four tests. the first three 'it' blocks pass successfully, the fourth is where I am stuck, I have added comments in that "it" block:
import angular from 'angular';
import AuthInterceptor from './auth.interceptor'
describe('Auth interceptor test', () => {
describe('AuthInterceptor test', () => {
let $httpBackend, $http, authInterceptor = AuthInterceptor();
beforeEach(angular.mock.module(($httpProvider, $provide) => {
$httpProvider.interceptors.push(AuthInterceptor);
$provide.factory('AuthService', () => ({
jwtToken: "hello",
backtoAuth: angular.noop
}));
}));
beforeEach(inject(function($injector) {
$httpBackend = $injector.get('$httpBackend');
$http = $injector.get('$http');
}))
it('should have a request function', () => {
let config = {};
expect(authInterceptor.request).to.be.defined;
expect(authInterceptor.request).to.be.a('function');
})
it('the request function should set authorization headers', (done) => {
$httpBackend.when('GET', 'http://jsonplaceholder.typicode.com/todos')
.respond([{
id: 1,
title: 'Fake title',
userId: 1
}]);
$http.get('http://jsonplaceholder.typicode.com/todos').then(function(transformedResult) {
expect(transformedResult.config.headers.Authorization).to.be.defined;
expect(transformedResult.config.headers.Authorization).to.contain('Bearer')
done();
})
$httpBackend.flush();
});
it('should have a responseError function', () => {
expect(authInterceptor.responseError).to.be.defined;
expect(authInterceptor.responseError).to.be.a('function');
//TODO: test return value
// see that AuthService.backToAuth()
})
it('the error function should call backtoAuth', (done) => {
//a url that doesn't give me a 401 like I'm hoping.
$httpBackend.when('POST', 'https://wwws.mint.com/overview.event').respond([
//what do I do here?
])
$http.post('https://wwws.mint.com/overview.event').then(function(transformedResult) {
console.log("success", transformedResult);
done();
}, function(error){
// I can't seem to get in here. if I can, the responseError should be called, which in turn calls backToAuth...
console.log("error", error);
done();
})
$httpBackend.flush();
});
The first respond parameter is status, it should be
$httpBackend.when('POST', 'https://wwws.mint.com/overview.event').respond(401);
It is always good to use Sinon/Jasmine spies/stubs instead of noops in stubbed methods, so their calls could be tested:
var sandbox;
beforeEach(() => {
sandbox = sinon.sandbox.create();
});
afterEach(() => {
sandbox.restore();
});
beforeEach(angular.mock.module(($httpProvider, $provide) => {
$httpProvider.interceptors.push(AuthInterceptor);
$provide.factory('AuthService', () => ({
jwtToken: "hello",
backtoAuth: sandbox.stub();
}));
}));
Related
I am using Jasmine & Karma for unit testing angular app . I have wrote unit test like this
it('should match request object', inject([UserService, MockBackend], (userService: UserService, mockBackend) => {
mockBackend.connections.subscribe((connection: MockConnection) => {
expect(connection.request.method).toEqual(RequestMethod.Post);
expect(connection.request.json().getUserProfileRequest).toEqual({
userid: '1234',
});
connection.mockRespond(new Response(new ResponseOptions({
body: UsersMockData.GET_USER_PROFILE,
})));
});
usersService.getUserProfile(1234)
.subscribe(data => {
expect(data).toBe(UsersMockData.GET_USER_PROFILE);
});
}));
Everything works fine and no issue now when I split test cases in to two separate test cases I write code like this
it('Check userProfile request', inject([UserService, MockBackend], (userService: UserService, mockBackend) => {
mockBackend.connections.subscribe((connection: MockConnection) => {
expect(connection.request.method).toEqual(RequestMethod.Post);
expect(connection.request.json()getUserProfileRequest).toEqual({
userid: '1234',
});
});
}));
it('check return data from service', inject([UserService, MockBackend], (userService: UserService, mockBackend) => {
connection.mockRespond(new Response(new ResponseOptions({
body: UsersMockData.GET_USER_PROFILE,
})));
usersService.getUserProfile(1234)
.subscribe(data => {
expect(data).toBe(UsersMockData.GET_USER_PROFILE);
});
}));
Both of these test cases are having expect statement but when I execute test cases , I see a message SPEC HAS NO EXPECTATIONS for both test cases . I am wondering why it shows spec has no expectations.
If you don't .subscribe to you method, the get request will never be made so the mock backend is never invoked. If you don't provide a mock response, the subscription to your method will never receive a value. Therefore, to reach the expectations at all, you must have a certain amount of minimal wiring in each test. In your case:
it('Check userProfile request', inject([UserService, MockBackend], (userService: UserService, mockBackend) => {
mockBackend.connections.subscribe((connection: MockConnection) => {
expect(connection.request.method).toEqual(RequestMethod.Post);
expect(connection.request.json().getUserProfileRequest).toEqual({
userid: '1234',
});
});
usersService.getUserProfile(1234).subscribe(data => {});
}));
it('check return data from service', inject([UserService, MockBackend], (userService: UserService, mockBackend) => {
mockBackend.connections.subscribe((connection: MockConnection) => {
connection.mockRespond(new Response(new ResponseOptions({
body: UsersMockData.GET_USER_PROFILE,
})));
});
usersService.getUserProfile(1234)
.subscribe(data => {
expect(data).toBe(UsersMockData.GET_USER_PROFILE);
});
}));
I have a test case that scope.action = 'Insert' need to be changed for every it() i.e for first test case it should be as insert for 2nd test case scope.action='update' and for the 3rd test case it should be scope.action='delete'. How can i achieve this.
'use strict';
describe('app module', function() {
beforeEach(module('sampleApp'));
beforeEach(module(function ($provide) {
$provide.value('BaseController', {});
}));
describe('TestController', function() {
var scope, controller;
beforeEach(inject(function($rootScope, $controller) {
scope = $rootScope.$new();
controller = $controller;
controller('BaseController', {$scope: scope});
controller('TestController', {
$scope: scope
});
scope.action="insert" ;
}));
it('Should return type insert', function () {
expect(scope.getActionType()).toBe('insert');
});
it('Should return type update', function () {
expect(scope.getActionType()).toBe('update');
});
it('Should return type delete', function () {
expect(scope.getActionType()).toBe('delete');
});
});
});
You'd achieve it via describe's in describe's. The outer beforeEach() block sets things up, but the inner ones set your scope:
describe('Test Controller', () => {
beforeEach(() => {
// In here, do what you currently have in your beforeEach
// but NOT the scope.action; EXCEPT the controller;
});
describe('Test 1', () => {
beforeEach(() => {
scope.action = 'Insert'
controller('TestController', { $scope: scope });
});
it('First Test', () => {});
});
describe('Test 2', () => {
beforeEach(() => {
scope.action = 'Update';
controller('TestController', { $scope: scope });
});
it('Second Test', () => {});
});
describe('Test 3', () => {
beforeEach(() => {
scope.action = 'Delete';
controller('TestController', { $scope: scope });
});
it('Third Test', () => {});
});
});
Parent Service:
module proj.Stuff {
export class ParentService {
//...properties, constructor, etc
public refreshStuff(id: number) {
this.childService
.getStuff(id)
.then((response) => this.stuff = response);
}
}
}
Child service:
module proj.Stuff {
export class ChildService{
//... properties, constructor, etc
public getStuff(id: number) {
var request: IPromise<any> = this.$http.get(
ChildService.apiUrlBase + "getStuff/" + id
);
return request
.then(response => {
return response.data.value;
}, response => {
this.$log.error("unable to get...");
});
}
}
}
Tests for the parent service:
describe("ParentService", () => {
// (property declarations omitted for brevity)
beforeEach(angular.mock.module(["$provide", ($provide) => {
var obj = {
getStuff: (id: number) => {
functionCalled = true;
return {
then: (callback) => {
return callback(["result"]);
}
};
}
};
$provide.value("ChildService", obj);
}]));
beforeEach(mock.inject((_$http_, _$log_, _$q_, _$httpBackend_, _$rootScope_, _ChildService_) => {
cService = _ChildService_;
pService = new ParentService(cbService);
}));
it("can be created", () => {
expect(pService).toBeDefined();
expect(pService).not.toBeNull();
});
it("can refresh stuff", () => {
pService.refreshStuff(1);
expect(pService.stuff).toEqual(["result"]);
expect(functionCalled).toBeTruthy();
// ***** what I want to do: *****
// expect(cService.getStuff).toHaveBeenCalled();
});
});
I'm wondering how can I spy on cService.getStuff instead of using the 'functionCalled' boolean?
When I try to spy on it, it complains that .then isn't defined - e.g. in the first beforeEach if I try spyOn(obj, "getStuff") it doesn't like it.
The tests pass as is, but would rather spyOn instead of using the boolean.
then method mocks are rarely justified, Angular DI allows to use unmocked promises and to focus on unit testing.
beforeEach(angular.mock.module(["$provide", ($provide) => {
// allows to inject $q, while $provide.value doesn't
$provide.factory("ChildService", ($q) => ({
// fresh promise on every call
getStuff: jasmine.createSpy('getStuff').and.callFake(() => $q.when('result'))
}));
}]));
Works best with Jasmine promise matchers, otherwise routine promise specs should be involved:
var result;
...then((_result) => { result = _result; })
$rootScope.$digest();
expect(result)...
I'm mocking a function called isLoggedIn():
auth = {
isLoggedIn: function () {
return true;
}
};
and apply is in a beforeEach loop
beforeEach(function () {
angular.mock.module(function ($provide) {
$provide.value('Auth', auth);
});
});
At the moment isLoggedIn() will always return true. This function needs to be able to return false for some particular tests.
Is there a way to pass a variable into the mock from my tests.
E.g. something like
var loggedIn = ;
auth = {
isLoggedIn: function () {
return loggedIn;
}
};
Yes, you can for instance put it inside your test:
var loggedIn;
beforeEach(function () {
auth = {
isLoggedIn: function () {
return loggedIn;
}
};
angular.mock.module(function ($provide) {
$provide.value('Auth', auth);
});
});
It is also possible to have it as a separate file, but for this example it seems overkill.
I'm trying to wrap the PushPlugin in a Angular factory, based on devgirls post, but so far without success.
angular.module('phonegap', [])
.factory('phonegapReady', function ($rootScope, $q) {
var loadingDeferred = $q.defer();
document.addEventListener('deviceready', function () {
$rootScope.$apply(loadingDeferred.resolve);
});
return function phonegapReady() {
return loadingDeferred.promise;
};
})
.factory('push', function ($rootScope, phonegapReady) {
return {
registerPush: phonegapReady().then(function (onSuccess, onError) {
// stripped handlers
if (device.platform === 'android' || device.platform === 'Android') {
pushNotification.register(
function () {
var that = this,
args = arguments;
if (onSuccess) {
$rootScope.$apply(function () {
onSuccess.apply(that, args);
});
}
},
function () {
var that = this,
args = {
'senderID': '123',
'ecb': 'onNotificationGCM'
};
if (onError) {
$rootScope.$apply(function () {
onError.apply(that, args);
});
}
}
);
} else {
pushNotification.register(
function () {
var that = this,
args = arguments;
if (onSuccess) {
$rootScope.$apply(function () {
onSuccess.apply(that, args);
});
}
},
function () {
var that = this,
args = {
'badge': 'true',
'sound': 'true',
'alert': 'true',
'ecb': 'onNotificationAPN'
};
if (onError) {
$rootScope.$apply(function () {
onError.apply(that, args);
});
}
}
);
}
})
};
});
Getting an error:
TypeError: '[object Object]' is not a function (evaluating 'e.registerPush(function(a){console.log("fun"),console.log(a)})')
What am I doing wrong?
When a you call then on a promise, it returns the promise so you can chain the callbacks.
I think wrapping registerPush with a function would work, like:
registerPush: function(onSuccess, onError) {
phonegapReady().then(function () {
// Do something with closured onSuccess and onError
});
},..