Unit testing angularjs - How to inject service dependency? - angularjs

I introduced a MyDataApiService dependency into ThingBuilderService, and now ThingBuilderService tests are failing. How do I mock MyDataApiService and tell ThingBuilderService about it in tests?
export default class ThingBuilderService {
public static $inject = ['MyDataApiService'];
public myData: any[];
/**
* Construct an instance of ThingBuilderService.
*
* #param {xyz.MyDataApiService} myDataApiService The MyDataApiService object.
*/
constructor(myDataApiService: xyz.MyDataApiService) {
myDataApiService.getSomeData()
.then((response) => this.myData = response.data);
}
//...
}
Test: (I've included code showing part of what I think I need to do, but I don't understand how to fit the pieces together.
describe('Thing Builder Service', () => {
var service;
var mockMyDataApiService = {};
beforeEach(() => {
var mockMyDataApiService.getSomeData = () => {
var deferred = $q.defer();
deferred.resolve({ data: [
{
'ItemId': 1010101,
'Description': 'asdfasdf'
},
{
'ItemId': 1010102,
'Description': 'jkjkjkjk'
}
]});
return deferred.promise;
};
// *********** now what? ***********
// and do I have to do something to make '$q'
// available in the getSomeData function?
angular.mock.module('abc.module');
angular.mock.inject(_ThingBuilderService_ => {
service = _ThingBuilderService_;
});
});
// tests here
it('should ...', () => { ... });
});
Test runs are giving errors like this:
Error: [$injector:unpr] Unknown provider: MyDataApiServiceProvider <-
MyDataApiService <- ThingBuilderService
and this:
Error: Base URL not defined for MyDataApiService

You have to provide your mocked service:
angular.mock.module(function($provide) {
$provide.service(„MyDatApiService“, mockMyDataApiService);
});

Related

unable to inject module/service for unit test

I am new to TDD and am trying to wire up a test, and have been stuck on it for hours. I keep getting the following error:
[$injector:modulerr] Failed to instantiate module AuthInterceptor due to:
Error: [$injector:nomod] Module 'AuthInterceptor' is not available! You either misspelled the module name or forgot to load it. If registering a module ensure that you specify the dependencies as the second argument.
http://errors.angularjs.org/1.5.8/$injector/nomod?p0=AuthInterceptor
at client/test/index.js:8237:13
at client/test/index.js:10251:18
at ensure (client/test/index.js:10175:39)
at module (client/test/index.js:10249:15)
at client/test/index.js:12786:23
at forEach (client/test/index.js:8490:21)
at loadModules (client/test/index.js:12770:6)
Here is my test:
import angular from 'angular';
import serviceModule from './auth.interceptor'
describe('wire.common.services', () => {
describe('AuthService', () => {
let AuthService;
beforeEach(angular.mock.module(serviceModule.name));
beforeEach(angular.mock.module(($provide) => {
$provide.factory('$q', () => ({}));
$provide.factory('$log', () => ({}));
}));
beforeEach(angular.mock.inject((_AuthService_) => {
AuthService = _AuthService_;
}));
it('should be a dummy test', () => {
expect(2).toEqual(2);
});
});
});
The actual code I'm testing:
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.backToDAS();
}
return $q.reject(rejection);
}
};
}
I don't understand why I'm getting this error - I provided all the dependencies for the service and am following what is outlined in the angular documentation. any help is appreciated!
Update, this is the code that I went with:
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",
backtoDAS: 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.backToDAS()
})
it('the error function should call backtoDAS', (done) => {
//the URL should be one that gives me a 401
$httpBackend.when('GET', 'https://wwws.mint.com/overview.event')
.respond([{
id: 1,
title: 'Fake title',
userId: 1
}]);
$http.get('https://wwws.mint.com/overview.event').then(function(transformedResult) {
console.log(transformedResult);
done();
}, function(error){
console.log(error);
done();
})
});
})
});
This means that AuthInterceptor Angular module wasn't defined (and by the way, relying on name is unsafe).
AuthInterceptor isn't a module but an injectable function. It can be tested in functional fashion as $http interceptor:
beforeEach(angular.mock.module(($httpProvider) => {
$httpProvider.interceptors.push(AuthInterceptor);
});
...
it('...', () => {
$httpBackend.when(...).respond(...);
$http.get(...).then((interceptedResult) => {
expect(interceptedResult)...
});
$rootScope.$digest();
});
or directly:
it('...', () => {
let interceptor = $injector.invoke(AuthInterceptor);
expect(interceptor).toEqual({
request: jasmine.any(Function),
requestError: jasmine.any(Function)
});
var config = { headers: {} };
interceptor.request(config);
expect(config)...
});
Services that produce side effects (AuthService, $log) should be stubbed.
This means that ng module is failing to load. :) And this happens while bootstrapping the app and ng module is first in a three element array: ng, ['$provide', function($provide) { ... }] and my own application module. It fails when loading the first one.
I've looked at console and I've copied this error message from it. There's no other error. None.
I hope you clicked that specific link and see that it doesn't give you any specific ideas about it. Unfortunately I've added this GitHub issue after exhausting other resources. I'm currently debugging angular code to get any further.

Jasmine unit tests with odata promises

I'm new at angular and at unit testing with angular. We are using odata for database CRUD actions, so we have created a service for that, looks like this:
function DatabaseService($http, $odataresource, DateFactory, constants) {
var url = constants.BACKEND.URL;
var ObjCreate = $odataresource(url + 'Objects/Function.CreateObject', {}, {}, {});
var service = {
createSomething: {
createObj: createObj
}};
return service;
function createObj(formData) {
var myObj = new ObjCreate();
mapData(formData, myObj );
return myObj.$save();
}
The code is a bit abstracted for my question, so don't wonder please. I want to unit test the function createObj() now, which doesn't work. I took an angular class and we learned there that for 'execute' promises we have to use $rootScope.digest(), but it doesn't seem to work in my case:
describe('createObj', function () {
it('should return data', inject(function ($rootScope) {
var DatabaseService = $injector.get('DatabaseService', { $odataresource: $odataresource });
var formDataMock = {
productName: "Produktname"
};
var test = 'abc';
DatabaseService.createSomething.createObj(formDataMock)
.then(function (data) {
test = data;
})
.catch(function (error) {
test = error;
});
$rootScope.$digest();
console.log(test);
}));
I have added the setting of the variable test to see when for example the then path is executed, but even with the $rootScope.$digest it will never step into the then path, my variable test will never change from 'abc' to something else.
Could you please give me a hint what am I doing wrong?
I tried to update your code to use the done Feature of Jasmine 2.0.
http://ng-learn.org/2014/08/Testing_Promises_with_Jasmine/
describe('createObj', function () {
it('should return data', function (done) {
var DatabaseService = $injector.get('DatabaseService', { $odataresource: $odataresource });
var formDataMock = {
productName: "Produktname"
};
var test = 'abc';
DatabaseService.createSomething.createObj(formDataMock)
.then(function (data) {
test = data;
})
.catch(function (error) {
test = error;
})
.finally(done);;
console.log(test);
});

Need help writing an Angular TypeScript Jasmine test for an Angular Service

I am attempting to write a test for an Angular Service using TypeScript. I'll be honest and say I have no idea what I'm doing as this is the first test I have ever written so any help would be appreciated.
What I have so far gives the following error:
TypeError: 'undefined' is not an object (evaluating 'this.httpAccessor.get(requestUrl).then')
Here is the service I am attempting to test:
/// <reference path="../../../typings/_references.ts" />
module app.service {
'use strict';
export interface IAvailability {
getAvailability(requirements: app.Requirements): ng.IPromise<any>;
}
export class Availability implements IAvailability {
static $inject = ['$filter', 'Service.HttpResourceAccessor'];
/**
* Constructor.
*/
constructor(private $filter: ng.IFilterService,
private httpAccessor: app.service.IHttpResourceAccessor) {
}
/**
* Returns availability promise
*/
getAvailability(requirements: app.Requirements): ng.IPromise<any> {
/**
* Generate the request url using the requirements
*/
var requestUrl = this.generateRequestUrlFromRequirements(requirements);
return this.httpAccessor.get(requestUrl).then((response) => {
var availability: app.ITripAvailability<app.ITripAvailability>response;
return availability;
});
}
/**
* Builds the request url using the requirements
*/
private generateRequestUrlFromRequirements(requirements: app.Requirements): string {
//The request string that will be returned after building
var requestUrl: string = '';
var baseAddress = 'http://localhost:8080/';
//Code emitted as this just builds a string and works fine
return requestUrl;
}
}
angular.module('app.service')
.service('Service.Availability', Availability);
}
Here is the test so far:
/// <reference path="../../../../typings/_references.ts" />
/// <reference path="../../../../typings/angularjs/angular-mocks.d.ts" />
/// <reference path="../../../../typings/jasmine/jasmine.d.ts" />
module app.service {
describe("app.service.IAvailability", () => {
'use strict';
var $filter: ng.IFilterService;
var mockAvailabilityService: app.service.Availability;
var mockHttpAccessor: app.service.IHttpResourceAccessor;
var requirements: app.Requirements;
beforeEach(() => {
angular.mock.module('app');
});
beforeEach(() => {
angular.mock.module('app.service');
});
/**
* Pre-test function before the test is executed
*/
beforeEach(() => {
requirements = new app.Requirements();
angular.mock.inject((_$filter_: ng.IFilterService) => {
$filter = _$filter_;
mockHttpAccessor = <app.service.IHttpResourceAccessor>jasmine.createSpyObj(
"httpAccessor", ["get"]);
mockAvailabilityService = new app.service.Availability(
<ng.IFilterService>$filter,
<app.service.IHttpResourceAccessor>mockHttpAccessor);
});
});
it('Should call httpAccessor.get', () => {
mockAvailabilityService.getAvailability(tripRequirements);
expect(mockHttpAccessor.get).toHaveBeenCalled();
});
//How do I check if promise has been resolved?
});
}
You are heading in the right direction...
describe('app.service.IAvailability', ()=>{
// declare dependencies and common vars
var mockFilter: ng.IFilterService,
mockHttpAccessor: app.service.IHttpResourceAccessor,
requirements: app.Requirements,
availabilityService: app.service.Availability;
// setup dependencies/mocks
beforeEach(()=>{
angular.mock.module('app');
angular.mock.inject((_$filter_: ng.IFilterService) => {
mockFilter = _$filter_;
});
mockHttpAccessor = <app.service.IHttpResourceAccessor>jasmine.createSpyObj("httpAccessor", ["get"]);
});
// helper function to create instance of the service
// (useful if dependencies change, only 1 function needs changed)
function createService(){
availabilityService = new app.service.Availability(
mockFilter,
mockHttpAccessor);
}
// not a great test, but shows how to use the setup above
it('should contain a getAvailability method', () => {
createService();
expect(availabilityService. getAvailability).toBeDefined();
});
});

What is the proper way to mock dependencies in Angular Services?

Setup: Karma, Browserify, Jasmine, Angular.
I'm trying to test an Angular service which, in turn, uses another service, which I'd like to mock.
I have the feeling that here I am getting another instance of my ItemLocalStorageService, created before mock injection took place.
ItemLocalStorageService.js
'use strict';
var _ = require('underscore'),
LOCALSTORAGE_LIST_KEY = 'items.list',
items;
module.exports = [
'StoreService',
function (StoreService) {
var getAll = function () {
if (StoreService.enabled) {
items = StoreService.get(LOCALSTORAGE_LIST_KEY);
}
return items;
};
var saveAll = function (latestItems) {
if (StoreService.enabled) {
StoreService.set(LOCALSTORAGE_LIST_KEY, latestItems);
}
items = latestItems;
};
return {
getAll: getAll,
saveAll: saveAll
};
}
];
ItemLocalStorageServiceTest.js
// Main application file. This is loaded here
// instead of being added to Karma's files array
// because of the Browserify setup.
require('scripts/app');
var mock = require('angular').mock;
describe('ItemLocalStorageService', function () {
var ItemLocalStorageService,
items,
mockStoreService;
beforeEach(mock.module('myApp'));
beforeEach(mock.module('myTemplates'));
beforeEach(function () {
items = [ {}, {}, {} ];
mockStoreService = {
'store': {
enabled: false,
get: jasmine.createSpy('store.get').and.returnValue(items),
set: jasmine.createSpy('store.set')
}
};
// Either of these two should work, I believe.
// 1
// mock.module({ 'StoreService': mockStoreService })''
// 2
mock.module(function($provide) {
$provide.value('StoreService', mockStoreService);
});
});
describe('Local storage', function () {
it('should return items from local storage', function () {
mock.inject(function (_ItemLocalStorageService_) {
ItemLocalStorageService = _ItemLocalStorageService_;
});
expect(ItemLocalStorageService.getAll()).toEqual(items);
expect(mockStoreService.store.get).toHaveBeenCalled();
});
});
});
Both expectations fail. The output of getAll is the actual content of the browser's local storage, not the mock output I provide. I'm obviously doing something wrong, so I'll gladly accept any pointers to lead me on the right way.

Unknown Provider Error while trying to test generic angularjs $resource service

I have generic service that create resources for my application:
(function(module) {
module.provider('restService', {
resourceRegistry: {},
addRestResource: function(entityName, entityProto) {
this.resourceRegistry[entityName] = entityProto;
},
$get: function() {
var restService;
for (var entityName in this.resourceRegistry) {
createRestResource(entityName, this.resourceRegistry[entityName]);
};
restService = {
//createRestResource: createRestResource
};
return restService;
}});
function createRestResource(entityName, entityProto) {
console.log('registering model: ' + entityName);
module.provider(entityName, { $get: function($resource, $http) {
var resource = $resource('/api/' + entityName + '/:id', { // TODO use config
id : '#id' //this binds the ID of the model to the URL param
},{
query : { method : 'GET', isArray : true }, //this can also be called index or all
update : { method : 'PUT' },
create : { method : 'POST' },
destroy : { method : 'DELETE' }
});
// here gose some other functionality unrelated to the topic...
return resource;
}});
}}(angular.module('restService', ['ngResource'])));
I can be used by any other module using
module.config(['restServiceProvider', function(restServiceProvider) {
restServiceProvider.addRestResource('User', { name: null, email: null });
}
And while above actually works for me in the application Actually above neither works for me in the application (it was working due to some code left from before refactoring) nor I cannot get working jasmine/karma test for it. The problem is that trying various method to configure restServiceProvider I always end with error stating that for eg TestEntity there is unknown provider TestEntityProider. Although I have tried different approaches to configure the resourceRegistry before the resource is being created, here is some test file.
describe('testing restService', function () {
var httpBackend;
var theRestServiceProvider;
beforeEach(function() {
module('ngResource');
module('restService');
var fakeModule = angular.module('test.app.config', ['ngResource'], function () {});
fakeModule.config( function (restServiceProvider) {
theRestServiceProvider = restServiceProvider;
restServiceProvider.addRestResource('TestModel', {testProp: null});
});
module('test.app.config');
});
beforeEach(function () {
inject(function ($httpBackend) {
httpBackend = $httpBackend;
})
});
beforeEach(inject(function (restService) {}));
describe('create restService entity', function() {
it('should post entity with nonempty testProp',
function() {
theRestServiceProvider.addRestResource('TestModel', {testProp: null});
inject(function(TestModel) {
var object = new TestModel();
object.testProp = 'John Doe';
httpBackend.expectPOST(/.*/,
function(postData) {
console.log("post data: " + postData);
jsonData = JSON.parse(postData);
expect(jsonData.testProp).toBe(object.testProp);
return jsonData;
}).respond({});
var response = object.$create();
httpBackend.flush();
});
});
});
});
IMO this is because within the test the registering resource is being done 'too late' but still I don't know how to do this right.
EDIT here is final resolution shortcut:
(function(module) {
module.provider('restService', function($provide) {
var provider = {};
// all the provider stuff goes here
function createRestResource(entityName, entityProto) {
/*
* using $provider here is fundamental here to properly create
* 'entityName' + Provider in the runtime instead of module initialisation
* block
*/
$provide.factory(entityName, function($resource, $http) { /* ... */ };
// do other stuff...
}
return provider;
}
}(angular.module('restServiceModule', ['ngResource'])))
I've made very similar thing here https://github.com/tunguski/matsuo-ng-resource/blob/master/matsuo-ng-resource.js, maybe it will help you.
Basically I'm adding new resource providers to $provide not to module. It works. I think it's main difference.
I am not very familiar with karma/jasmine but BDD syntax seems to be similar.
In my understanding you should have something like this
beforeEach(function () {
module('restService');
inject(function (_$httpBackend_, _restService_, _ngResource_) {
$httpBackend = _$httpBackend_;
restService = _restService_;
ngResource = _ngResource_;
})
});
instead of all you beforeEach. Read more here.
If you want to provide mocks instead of injected services you would use
module('restService', function($provide){
$provide.value('serviceToMock', mockObject);
});
Note that naming module and provider/service/factory with same name may be confusing at later stage...

Resources