I am writing the test case for testing a service call in angular. I am returning a variable value in the resolve. My issue is that i am getting an undefined value as a response after the promise is resolved. following is the piece of code i am using.
resolve: {
peopleData: ($q, service, apiOperations, errorService) => {
const getHelloWorld = () => {
{
return‘ hello - world ';
}
};
const errorHandler = (error, operation) => {
var deferred = $q.defer();
errorService.handle(error, operation);
deferred.resolve({});
return deferred.promise;
};
service.getPeopleName().then((response) => {
console.log('called then method');
if (!angular.isUndefined(response)) {
return response;
} else {
return {};
}
}).catch((error) => {
errorHandler(error, apiOperations.GET_PEOPLE);
return {};
});
}
Now my test case is
describe('state peopleService application', function() {
let $q, authService, $state, state_name = 'main.application',
result, $rootScope, applicationService;
let errorService = {
handle: function(error, operation) {}
};
let apiOperations = {
GET_People: ‘getPeople ',
TEST_ERROR: 'testError'
};
angular.module('mock.Service', []).service('mockPeopleService', function() {
var peopleService = {};
peopleService.getPeople = function() {
let deferred = $q.defer();
deferred.resolve(‘dummy response ');
return deferred.promise;
}
return applicationService;
}); beforeEach(angular.mock.module(home)); beforeEach(angular.mock.module('mock.Service')); beforeEach(angular.mock.inject(function(_$q_, _$state_, _mockPeopleService_, _$rootScope_) {
$q = _$q_;
$state = _$state_;
$rootScope = _$rootScope_;
peopleService = _mockPeopleService_;
})); it('should load the data', () => {
let state = $state.get(state_name);
let result = state.resolve.peopleData($q, peopleService, apiOperations, errorService);
$rootScope.$apply();
console.log('result ' + result);
});
})
});
The log.console is returning result undefined
Remember that in an arrow function with {curly braces}, a return needs to be explicit, just like in regular functions. So with resolve.peopleData() as currently written, undefined is hardly surprising.
Adding a return and tidying, you might end up with something like this :
resolve: {
peopleData: ($q, service, apiOperations, errorService) => {
return service.getPeopleName().then(response => {
^^^^^^
if (angular.isUndefined(response)) {
throw new Error('response is undefined');
}
return response;
}).catch(error => {
errorService.handle(error, apiOperations.GET_PEOPLE);
return {};
});
}
}
Now, the test stands at least a chance of working.
Related
I'm trying to test an angular component using $componentController
I'm only trying to test inputs and outputs of public code, so in my controller the "this.data" property is highest priority. Starting with input of this.data is easy as it's just testing the class constructor prop, which is by default set to undefined.
The output is the problem, it has a lot of code to call through before getting the expected value.
Question:
How do I attach to $q.all.resolve() and test the this.data's mapped output? The expectation is that expect(ctrl.data).toEqual(expectedData).
Controller.js:
class Controller {
/**
* #ngInject
*/
constructor(
someService, $q, $scope) {
this.ngScope_ = $scope;
this.someService_ = someService;
this.ngQ_ = $q;
this.data = undefined;
}
$onInit() {
this.getData_();
}
getData_() {
let requests = [1,2];
const REQUEST_TYPE = 'SOME_REQUEST';
const outEachApproval = (d) => d[0].approvals;
const requestsAsArr = requests.split(',');
const promiseArr = requestsAsArr.map(
(i) => this.someService.getAllDataByType(
REQUEST_TYPE, i));
this.ngQ_.all(promiseArr).then((data) => {
this.data = data.map(outEachApproval); // How the hell do I test this.data at this point in time in the unit test?
});
}
Controller_test.js
describe('someModule', ()=> {
let $componentController,
ctrl,
$rootScope,
$q;
const mockData = [[{approvals: [{name: 'hey'}]},{approvals: [{name: 'hey'}]}]];
const expectedData = [[{name:'hey'}],[{name:'hey'}]];
beforeEach(() => {
module(someModule.name);
inject((_$componentController_, _$rootScope_, _$q_) => {
$componentController = _$componentController_;
$q = _$q_;
$rootScope = _$rootScope_;
});
ctrl = $componentController(COMPONENT_NAME,
{
$scope: $rootScope.$new(),
someService: {
getAllDataByType: () => {
return Promise.resolve(mockData);
}
}
}, {});
});
describe('this.data input', ()=> {
it('should be undefined', () => {
expect(ctrl.data).toBeUndefined();
});
});
describe('this.data output', ()=> {
it('should be equal to expectedData after init', (done) => {
ctrl.$onInit();
expect(ctrl.data).toEqual(expectedData);
ctrl.ngScope_.$apply();
done();
});
});
});
I see several ways to deal with it:
First
First let your someService mock to be available in your tests:
let someServiceMock = {
getAllDataByType: () => {
//we can leave this empty
}
};
...
beforeEach(() => {
...
ctrl = $componentController(COMPONENT_NAME,
{
$scope: $rootScope.$new(),
someService: someServiceMock
}, {});
});
Then in your tests simply spy on it:
describe('this.data output', ()=> {
it('should be equal to expectedData after init', (done) => {
//as we use $q.all(), we need to create array of promises
let deferred1 = $q.defer();
...
let deferredN = $q.defer();
let arrayOfPromises = [deferred1.promise, ... ,deferredN.promise];
//then we spy on needed method and mock its return value
spyOn(someServiceMock, 'getAllDataByType').and.returnValue(arrayOfPromises);
ctrl.$onInit();
expect(someServiceMock.getAllDataByType).toHaveBeenCalled();
//now we resolve our promises with any data we want
let resolveData1 = /*Promise#1 mocked data*/;
deferred1.resolve(resolveData1)
let resolveData2 = /*Promise#2 mocked data*/;
deferred2.resolve(resolveData2)
...
let resolveDataN = /*Promise#N mocked data*/;
deferredN.resolve(resolveDataN)
//$q.all() returns array, so expectedData would be array
let expectedData = [
resolveData1,
resolveData2,
...
resolveDataN
];
//then we need to apply changes
$rootScope.$apply();
expect(ctrl.data).toEqual(expectedData);
ctrl.ngScope_.$apply();
done();
});
});
Here is a simple plunker to play with multiple $q.defer() instances.
Btw, if requests in someServiceMock.getAllDataByType() are implemented via angular $http, then you can mock them with $httpBackend service.
Second
Create a mock for $q service in your component controller:
let $qMock = {
all: () => {
//we can leave this empty
}
}
...
beforeEach(() => {
...
ctrl = $componentController(COMPONENT_NAME,
{
$scope: $rootScope.$new(),
someService: {
getAllDataByType: () => {
return Promise.resolve(mockData);
}
},
$q: $qMock
}, {});
});
Then in your tests simply spy on it:
describe('this.data output', ()=> {
it('should be equal to expectedData after init', (done) => {
let deferred = $q.defer();
spyOn($qMock, 'all').and.returnValue(deferred.promise);
ctrl.$onInit();
expect($qMock.all).toHaveBeenCalled();
deferred.resolve(expectedData);
//now we need to apply changes
$rootScope.$apply();
expect(ctrl.data).toEqual(expectedData);
ctrl.ngScope_.$apply();
done();
});
});
This test is simpler, but notice that it doesn't depend on what someService.getAllDataByType() will return.
Some useful articles on subject:
https://docs.angularjs.org/api/ng/service/$q
http://www.bradoncode.com/blog/2015/07/13/unit-test-promises-angualrjs-q/
Given the following test.
How do I ensure that the promise is resolved, and the data is provided.
describe("component: FicaStatusComponent",
function () {
var fs;
beforeEach(function () {
module("aureus",
function ($provide) {
$provide.service("ficaService", function () {
this.status = function () {
return $q(function (resolve, reject) {
resolve([{ documentType: { id: 1 } }]);
});
}
})
});
});
beforeEach(inject(function (_$componentController_, _ficaService_) {
$componentController = _$componentController_;
fs = _ficaService_;
}));
it("should expose a `fica` object", function () {
console.log('should expose');
var bindings = {};
var ctrl = $componentController("ficaStatus", null, bindings);
expect(ctrl.fica).toBeDefined();
});
it("compliant with no documents should not be compliant",
function () {
var ctrl = $componentController("ficaStatus");
expect(ctrl.fica.length).toEqual(1);
});
}
);
The second test compliant with no documents... is failing. The length is zero. The other test is passing, so I have the correct controller being instantiated, the property is defined.
The mock service is not resolving the data correctly, probably because the Promise is still executing, or not being called at all.
Here is the implementation of the controller for the component:
var FicaStatusController = (function () {
function FicaStatusController($log, $loc, ficaService) {
var _this = this;
this.$log = $log;
this.$loc = $loc;
this.ficaService = ficaService;
this.fica = [];
this.ficaService.status(1234).then(function (_) { return _this.fica = _; });
}
The service is as follows:
var FicaStatusService = (function () {
function FicaStatusService($log, $http) {
this.$log = $log;
this.$http = $http;
}
FicaStatusService.prototype.status = function (accountNumber) {
var url = "api/fica/status/" + accountNumber;
this.$log.log("status: " + url);
return this.$http
.get(url)
.then(function (_) { return _.data; });
};
return FicaStatusService;
}());
...
First, u can use $q like:
this.status = function () {
return $q.when([{ documentType: { id: 1 } }]);
}
Second, to resolve promise use $scope.$digest, $rootScope.$digest:
var a = $q.when({test: 1});
expect(a.test === 1).toBe(false);
$rootScope.$digest();
expect(a.test === 1).toBe(true);
I am creating a service called ActiveUserProfileService, but when I call its function in a controller I get undefined as a result and I cannot figure out why. The strangest part is that, in the ActiveUserProfileService service, the information from the UserService is displayed through console.log, so I'm receiving the information, but after calling the ActiveUserProfileService in the controller, it gives me undifened. It seems like the data isn't passed around. Can someone help me ?
UserService.js:
(function () {
'use strict';
angular
.module('myApp')
.factory('UserService', UserService);
UserService.$inject = ['$http'];
/* #ngInject */
function UserService($http) {
var service = {
getAuthenticatedUser: getAuthenticatedUser,
getUserInformation: getUserInformation
};
return service;
function getUserInformation(idUser) {
return $http.post('api/user/details', {idUser: idUser});
}
function getAuthenticatedUser() {
return $http.get('api/user');
}
}
})();
ActiveUserProfileService.js
(function () {
'use strict';
angular
.module('myApp')
.factory('ActiveUserProfileService', ActiveUserProfileService);
ActiveUserProfileService.$inject = ['$http','UserService'];
/* #ngInject */
function ActiveUserProfileService($http, UserService) {
var service = {
isAccount: isAccount
};
return service;
////////////////
function isAccount(accountName) {
UserService.getAuthenticatedUser()
.then(function (response) {
var data = response.data;
UserService.getUserInformation(data.user.id)
.then(function (response) {
var userDetails = response.data;
console.log("It is");
console.log(accountName == userDetails.account_types[0].description_internal);
return accountName == userDetails.account_types[0].description_internal;
});
})
}
}
})();
My controller:
(function () {
'use strict';
angular
.module('myApp')
.controller('WizardController', WizardController);
WizardController.$inject = [
'UserService',
'ActiveUserProfileService'
];
/* #ngInject */
function WizardController(UserService,ActiveUserProfileService) {
var vm = this;
console.log("ActiveUserProfileService");
console.log(ActiveUserProfileService.isAccount("professional")); //is Returning me undefined
}
})
();
The point is, you're trying to return a value for isAccount inside another function, a callback. When you do that, you're returning a value to this function, and not isAccount itself, so isAccount will not return anything, undefined, then.
As you are calling an assynchronous method, then isAccount must be assynchronous as well,
Replace
function isAccount(accountName) {
UserService.getAuthenticatedUser()
.then(function (response) {
var data = response.data;
UserService.getUserInformation(data.user.id)
.then(function (response) {
var userDetails = response.data;
console.log("It is");
console.log(accountName == userDetails.account_types[0].description_internal);
return accountName == userDetails.account_types[0].description_internal;
});
})
}
with
function isAccount(accountName) {
var deferred = $q.defer();
UserService.getAuthenticatedUser()
.then(function (response) {
var data = response.data;
//when the user is loaded, then you resolve the promise you has already returned
UserService.getUserInformation(data.user.id)
.then(function (response) {
var userDetails = response.data;
console.log("It is");
console.log(accountName == userDetails.account_types[0].description_internal);
deferred.resolve(accountName == userDetails.account_types[0].description_internal);
return; //here is not isAccount return, but anonymous inner function 'function (response)', you got it?
});
});
return deferred.promise; //return as soon as creates the promise
}
For sure you'd have to inject $q service as well
I'm trying to test an Angular controller and mock a factory so that I can use it within this same test. I'm fairly new to Angular testing & have been having trouble figuring out how to this. My factory, doesn't use the $http rather the $q service, returns a promise. I'm also unsure of what to put inside my mock factory given that the factory's function calls return a promise.
My end goal is to call my mock factory from my controller and then check the two arrays in my controller for the data which is supposed to populate them. If you have any tips for restructuring my tests for testability, please do give feedback.
Angular Controller
export class workListController {
constructor(dataService, $q) {
this.$q = $q;
this.work = [];
this.tasks = [];
this.dataService = dataService;
this.setup();
}
setup() {
this.$q.all([this.dataService.getWorkItems(), this.dataService.getTasks()])
.then(() => {
this.work = this.dataService.getState().work;
this.tasks = this.dataService.getState().tasks;
this.dataService.addNumberOTasksToWork();
});
}
tasksForWork(workId) {
var workTasks = [];
for (let task of this.tasks) {
if (task.agf__Work__c === workId) {
workTasks.push(task);
}
}
return workTasks;
};
}
Angular Factory
const dataService = ($q) => {
let work = [];
let tasks = [];
let connection = new Connection{/**/};
return { getWorkItems, getTasks, addNumberOTasksToWork, getState};
function queryWrapper(query) {
var deferred = $q.defer();
connection.query(query)
.then(function(result) {
deferred.resolve(result);
}, function(error) {
deferred.reject(error);
});
return deferred.promise;
}
function getWorkItems() {
return queryWrapper(`SELECT Id, ......`)
.then((data) => {
//data looks like this: {totalSize: 3, done: true, records: [......]}
work = data.records;
});
}
function getTasks() {
return queryWrapper(`SELECT Id,...`)
.then((data) => {
//data looks like this: {totalSize: 3, done: true, records: [......]}
tasks = data.records;
});
}
function addNumberOTasksToWork() {
work.forEach((workItem) => {
workItem.numberOfTasks = 0;
});
work.forEach((workItem) => {
tasks.forEach((taskItem) => {
if (taskItem.agf__Work__c === workItem.Id) {
workItem.numberOfTasks++;
}
});
});
}
function getState(){
return {work,tasks};
}
};
export {dataService};
Test file
import {workList} from './work-list.module.js';
import {workListDirective} from './work-list.directive.js';
import template from './work-list.html';
import {workListController} from './work-list.controller.js';
describe('AA_TaskBoard - workList', function () {
let $scope;
let $controller;
let $httpBackend;
let mockDataService;
beforeEach(angular.mock.module(workList.name));
//trying to mock factory
beforeEach(angular.mock.module(function($provide) {
$provide.value('dataService', mockDataService);
mockDataService = {
getWorkItems: function(){
//this should return a promise, but unsure of what to put here
return {
};
},
getTasks: function(){
return {
};
}
};
}));
beforeEach(inject(function(_$rootScope_, _$controller_, _$httpBackend_) {
$rootScope = _$rootScope_;
$controller = _$controller_;
$httpBackend = _$httpBackend_;
}));
});
The trouble comes when creating the provider for the mock dependency, as it needs to use $q, which is another service within angular, and these cannot be accessed when setting up the provider.
Imagine that we have a Factory we want to test:
angular.module('myApp').factory('MyFactory', function (MyDependency, $q) {
return {
doSomething: function () {
var deferred = $q.defer();
MyDependency.doAction().then(function (response) {
deferred.resolve(response);
// other events
}, function (error) {
deferred.reject(error);
// other events
});
return deferred.promise;
}
}
});
And the following unit test:
describe('Service: MyFactory', function () {
var myDependency, myFactory;
beforeEach(module('myApp'));
// The problem is here, as $q cannot be instantiated
// when setting up providers, and our mock service we are
// creating as the dependency for MyFactory requires $q
beforeEach(module(function ($provide, $q) {
var promise = $q.defer().promise;
myDependency = jasmine.createSpyObj('MyDependency', ['open']);
myDependency.open.andReturn(promise);
$provide.value('MyDependency', {
doAction: myDependency.open
});
}));
beforeEach(inject(function (MyFactory) {
myFactory = MyFactory;
}));
describe('MyDependency.doAction should be called', function () {
myFactory.doSomething();
expect(myDependency.open).toHaveBeenCalled();
// expect other events
});
});
MyDependency has a function, open, that we need to watch and override the method with a custom promise that we will control the data being resolved and rejected. We can easily create the mock dependency that will be injected into MyFactory, but how can we access other services, like $q during this phase?
The only reasonable solution I've come up with is to set up the provider like so, but it gives us much less control and more workarounds to handle success vs failure compared to promise.reject() promise.resolve()
beforeEach(module(function ($provide) {
myDependency = jasmine.createSpyObj('MyDependency', ['doAction']);
myDependency.doAction.andCallFake(function (){
return {
then: function (success, err){
success.call(this);
}
};
});
$provide.value('MyDependency', {
open: myDependency.open
});
}));
Assumptions
I know almost nothing about MyDependency service
It is promise
I can only test behaviour of resolve or reject
The most strange line is $provide.value('MyDependency', {});. This is proof of concept, any comments are welcome.
First revision of the implementation
(function() {
angular.module('myApp', []).factory('MyFactory', function(MyDependency, $q) {
return {
doSomething: function() {
var deferred = $q.defer();
MyDependency.doAction().then(function(response) {
deferred.resolve(response);
}, function(error) {
deferred.reject(error);
});
return deferred.promise;
}
};
});
})();
describe('myApp', function() {
var MyFactory, MyDependency = {},
$q, scope;
beforeEach(function() {
module('myApp');
});
beforeEach(function() {
module(function($provide) {
$provide.value('MyDependency', {});
});
});
beforeEach(inject(function(_MyFactory_, _$q_, $rootScope) {
MyFactory = _MyFactory_;
$q = _$q_;
scope = $rootScope.$new();
}));
describe('MyDependency', function() {
var MyDependencyDefer;
beforeEach(inject(function(MyDependency, $q) {
MyDependencyDefer = $q.defer();
MyDependency.doAction = jasmine.createSpy('MyDependency.doAction').and.returnValue(MyDependencyDefer.promise);
}));
it('resolves doAction()', function() {
var stubSuccess = 'mock data';
var doSomethingDefer = MyFactory.doSomething();
MyDependencyDefer.resolve(stubSuccess);
doSomethingDefer.then(function(r) {
expect(r).toBe(stubSuccess);
});
scope.$digest();
});
it('rejects doAction()', function() {
var stubError = 'reason error';
var doSomethingDefer = MyFactory.doSomething();
MyDependencyDefer.reject(stubError);
doSomethingDefer.catch(function(r) {
expect(r).toBe(stubError);
});
scope.$digest();
});
});
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<link href="//safjanowski.github.io/jasmine-jsfiddle-pack/pack/jasmine.css" rel="stylesheet" />
<script src="//safjanowski.github.io/jasmine-jsfiddle-pack/pack/jasmine-2.0.3-concated.js"></script>
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular-mocks.js"></script>
Second revision of the implementation (after two years of development)
(() => {
angular.module('myApp', []).factory('MyFactory', ['MyDependency', MyDependency => {
return {
doSomething: () => MyDependency.doAction()
}
}])
})()
describe('MyFactory.doSomething() returns MyDependency.doAction()', () => {
'use strict';
const promiseResponse = {
succ: 'success',
err: 'error'
}
let MyFactory, MyDependency = {},
$q, scope
beforeEach(module('myApp'));
beforeEach(() => {
module(['$provide', $provide => {
$provide.value('MyDependency', {})
}]);
});
beforeEach(inject(['MyFactory', '$q', '$rootScope', (_MyFactory_, _$q_, $rootScope) => {
MyFactory = _MyFactory_
$q = _$q_
scope = $rootScope.$new()
}]));
describe('MyDependency.doAction() returns promise', () => {
let MyDependencyDefer, doSomethingDefer
beforeEach(inject(['MyDependency', '$q', (MyDependency, $q) => {
MyDependencyDefer = $q.defer()
MyDependency.doAction = jasmine.createSpy('MyDependency.doAction').and.returnValue(MyDependencyDefer.promise)
}]))
beforeEach(() => {
doSomethingDefer = MyFactory.doSomething()
})
it('that can be resolved', done => {
MyDependencyDefer.resolve(promiseResponse.succ)
doSomethingDefer.then(r => {
expect(r).toBe(promiseResponse.succ)
done()
});
scope.$digest()
});
it('that can be rejected', done => {
MyDependencyDefer.reject(promiseResponse.err)
doSomethingDefer.catch(r => {
expect(r).toBe(promiseResponse.err)
done()
})
scope.$digest()
})
})
})
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<link href="//safjanowski.github.io/jasmine-jsfiddle-pack/pack/jasmine.css" rel="stylesheet" />
<script src="//safjanowski.github.io/jasmine-jsfiddle-pack/pack/jasmine-2.0.3-concated.js"></script>
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular-mocks.js"></script>