Testing RxJS using TestScheduler with Promises and angular - javascript

I'm having a small issue using RxJS and Angular (not Angular 2) that I'm sure indicates I'm just doing something wrong, but I'm not sure exactly what.
I have a function that creates an rx.Observable stream that I would like to test. A simplified version of the function is below:
ResourceCollection.prototype.rxFetch = function() {
var scheduler = this.injectedScheduler;
var result = functionThatReturnsAnObservable(theseParams).concatMap(function(items) {
var promises = _.map(readFromExternal(items), function(promise) {
// results of this promise should be ignored
return Rx.Observable.fromPromise(promise, scheduler);
});
promises = promises.concat(_.map(items, function(item) {
// callEvent returns EventResult, these values should be passed on
return Rx.Observable.fromPromise(callEvent(item), scheduler);
}));
return promises;
}).concatMap(function(x) { return x; }).filter(function(res) {
return (res instanceOf EventResult);
}).toArray();
return result;
});
My test function looks like this:
describe('query', function() {
var customers;
var scheduler;
beforeEach(function() {
scheduler = new Rx.TestScheduler();
customers = new ResourceCollection({
url: '/api/customers',
keyName: 'CustomerId',
globalActions: {
rxQuery: { method: 'GET', isArray: true }
}
});
$httpBackend.whenGET('/api/customers/rxQuery').
respond(function() {
return [200, [
{ CustomerId: 1, Name: 'Brian', Region: 'North' },
{ CustomerId: 2, Name: 'Ravi', Region: 'East' },
{ CustomerId: 3, Name: 'Ritch', Region: 'East' },
{ CustomerId: 4, Name: 'Jeff', Region: 'West' },
{ CustomerId: 5, Name: 'Brandon', Region: 'West' }
]];
});
});
it('rxFetch customers', function(done) {
var vals;
customers.injectedScheduler = scheduler
var result = customers.rxFetch();
result.subscribe(function(values) {
vals = values;
});
$httpBackend.flush();
// my question is here - what can I do to get rid of this loop?
while (vals == null) {
scheduler.advanceBy(100);
$rootScope.$apply();
}
scheduler.start();
expect(vals.length).toEqual(5);
expect(vals[0]).toBe(customers[0]);
done();
});
});
The issue is a simple one - while the while loop in the test is in there, the test will produce the correct results (which is an array that contains the results of all the callEvent functions). Replace the while loop with a scheduler.scheduleAbsolute (or some other such call) combined with a $rootScope.$apply, and only one of the promises from the callEvent function will complete. Call it twice, and two of them will complete, etc (hence the while loop).
But the while loop is pretty ugly - and I'm sure there has to be an cleaner way to get this test to pass. Many thanks to anyone who can point me in the correct direction.

Related

Issue returning result from a function

Hi I cannot return information from a function? When I try I always receive an 'undefined' message.
When I console.log the variables within the function the information appears to be correct.
The purpose of the function is to add sales data for an employee to the employee object.
This is what should happen -
{
id: 3,
firstName: 'Fred',
lastName: 'Jones',
gender: 'Non-Binary',
age: 54,
position: 'Salesperson',
sales: [{
staffId: 3,
item: 'Pre-built PC',
price: 1999.95,
date: '02-09-2022'
},
{
staffId: 3,
item: 'USB Cable',
price: 5,
date: '02-09-2022'
},
{
staffId: 3,
item: 'HDMI Cable',
price: 15.45,
date: '02-09-2022'
}
]
}
This is the function that I have -
function mapEmpAndSales() {
employeesData.map(function(employee) {
let newEmpInfo = Object.assign({}, employee);
// console.log("1");
// console.log(newEmpInfo.id);
newEmpInfo.sales = salesData.filter(function(element) {
return element.staffId == employee.id;
});
// console.log("2");
// console.log("XXXXXXXX");
// console.log(newEmpInfo);
return newEmpInfo;
// result = newEmpInfo;
});
// console.log(result);
// return result;
}
const finalresult = mapEmpAndSales();
// let newInfo = mapEmpAndSales();
console.log("XXXXXXX");
console.log(finalresult);
This line:
// return result;
is necessary to pass data back out through your function. It should not be commented out. But I don't think you want to return result, rather return employeesData variable.
return employeesData;
Your mapEmpAndSales didn't return any thing, you need return the value of employeesData.map(...).
function mapEmpAndSales() {
return employeesData.map(function(employee) {
// ....
});
}
I think is because you aren't returning in the function 'mapEmpAndSales', if you see your return is inside the 'function(employee){...}'. So, you create this function 'function(employee)' and return in that, but in the body of your main function have no return.
Try creating a const outside the map function like 'const result = []', and in your map function you add the result at it and in your main function your really return the result.
Like this:
function mapEmpAndSales() {
const result = [];
employeesData.map(function(employee) {
let newEmpInfo = Object.assign(Object.assign({}, employee));
newEmpInfo.sales = salesData.filter(function(element) {
return element.staffId == employee.id;
});
result.push(...newEmpInfo); // IF IT RETURNS A ARRAY
result.push(newEmpInfo); //IF IT RETURNS ONLY ONE VALUE
});
return result;
}
The map function returns an array, which should be captured ad returned from the mapEmpAndSales function.
The solution is already in your code, just simply uncommenting the //return result; line as the following:
function mapEmpAndSales() {
let result = employeesData.map(function(employee) {
let newEmpInfo = Object.assign({}, employee);
newEmpInfo.sales = salesData.filter(function(element) {
return element.staffId == employee.id;
});
return newEmpInfo;
});
return result;
}
const finalresult = mapEmpAndSales();
console.log(finalresult);

how to chain two http calls in AngularJS?

I'm trying to chain two http calls. The first one returns a set of records and then I need to get finance data for each of them.
flightRecordService.query().$promise.then(function (flightRecords) {
$scope.flightRecords = flightRecords;
for (var i = 0; i < $scope.flightRecords.length; i++) {
$scope.flightRecords[i].financeDocument =
financeDocumentService
.isReferencedDocumentIdCompensated({
id: $scope.flightRecords[i].id
}).$promise.then(
function (data) {
return ({
'isCompensated': data.headers['compensated']
});
}
);
console.log($scope.flightRecords);
}
});
This is the FlightRecord object:
$$hashKey: "object:27"
aircraft: {id: 100, registration: "LV-OEE", model: "152", status: "ACTIVE", brand: "Cessna", …}
amountOfHours: 1
canceled: false
closed: false
crew: [Object] (1)
destiny: null
endFlight: "2017-01-06T20:54:05.296"
financeDocument: d
--> $$state: {status: 1, value: {isCompensated: "false"}}
--> d prototipo
id: 100
landings: 0
nature: "LDI"
opened: true
origin: null
purpose: "VP"
startFlight: "2017-01-06T19:44:05.296"
status: "OPENED"
type: "ENT"
financeDocument object has not the structure I expect... I need a the following format:
...
endFlight: "2017-01-06T20:54:05.296"
financeDocument: { isCompensated: "false" }
id: 100
...
What I need to change to get that?
Thanks a lot!!
What you'll want to do is modify each "flight record" entry when you've retrieved the extra details. You'll also probably want to use $q.all to signal to the caller that the operation is complete.
const promise = flightRecordService.query().$promise.then(flightRecords => {
return $q.all(flightRecords.map(flightRecord => {
return financeDocumentService.isReferencedDocumentIdCompensated({
id: flightRecord.id
}).$promise.then(data => Object.assign(flightRecord, {
isCompensated: data.headers.compensated
}))
}))
})
promise.then(flightRecords => {
$scope.flightRecords = flightRecords
})
Why not just set it on the original object?
flightRecordService.query().$promise.then(function (flightRecords) {
$scope.flightRecords = flightRecords;
for (var i = 0; i < $scope.flightRecords.length; i++) {
(function(record) {
financeDocumentService
.isReferencedDocumentIdCompensated({
id: $scope.flightRecords[record].id
}).$promise.then(
function (data) {
$scope.flightRecords[record].financeDocument = {
'isCompensated': data.headers['compensated']
}
});
})(i)
console.log($scope.flightRecords);
}
});
You are trying to set the financeDocument property synchronously using a Promise. You need to set the variable in the success callback of the promise.

Find duplicate object from one collection to another collection - using 2 arrays in knockout JS

In knockout JS I want to find out 1st duplicate object from my collection and return that object as modal. I have to check for 1st duplicate object from first array aginst 2nd Array based on my condition. Tried _findWhere & _.Some & _.each nothing worked. Can someone help
Here -- MyMainModal is my Moda which will have multiple objects
self.dupRecord= function (MyMainModal) {
var Modaldata= ko.mapping.toJS(MyMainModal);
return _.some(Modaldata, function (MD1) {
return _.some(Modaldata, function (MD2) {
if ((MD1.ID!== MD2.Id) &&
(MD1.Name === MD2.name));
});
});
};
How about incorporating the check for first duplicate into the mapping? Something like:
function Child(data) {
ko.mapping.fromJS(data, {}, this);
};
var model = {
children: [{
id: '1',
name: 'Billy'
}, {
id: '2',
name: 'Susy'
}]
};
var mapping = {
children: {
key: function(data) {
return ko.utils.unwrapObservable(data.id);
},
create: function(options) {
console.log('creating ' + options.data.name, options.parent);
var newChild = new Child(options.data);
if(options.parent.firstDuplicate() === undefined)
options.parent.children().forEach(function(child) {
if(child.name() === newChild.name())
options.parent.firstDuplicate([child, newChild]);
});
return newChild;
},
update: function(options) {
console.log(' updating ' + options.data.name);
return options.target;
}
}
};
var vm = {
children: ko.observableArray(),
firstDuplicate: ko.observable()
};
ko.mapping.fromJS(model, mapping, vm);
ko.applyBindings(vm);
model.children.push({
id: 3,
name: 'Billy'
});
setTimeout(function() {
console.log('--remapping--');
ko.mapping.fromJS(model, mapping, vm);
}, 2000);
I read that as, "if we're not updating the record, potentially set the first duplicate." Here's a fiddle: http://jsfiddle.net/ge1abt6a/

How can I unit test a complex promise chain?

I have some JavaScript code:
var findLeastUsedPassage;
findLeastUsedPassage = function(StudentId) {
var passageCounts;
passageCounts = [];
return db.Passage.findAll({
where: {
active: true
}
}).each(function(dbPassage) {
var passage;
passage = dbPassage.get();
passage.count = 0;
return passageCounts.push(passage);
}).then(function() {
return db.Workbook.findAll({
where: {
SubjectId: 1,
gradedAt: {
$ne: null
},
StudentId: StudentId
},
include: [
{
model: db.WorkbookQuestion,
include: [db.Question]
}
],
limit: 10,
order: [['gradedAt', 'DESC']]
});
}).each(function(dbWorkbook) {
return Promise.resolve(dbWorkbook.WorkbookQuestions).each(function(dbWorkbookQuestion) {
var passageIndex;
passageIndex = _.findIndex(passageCounts, function(passageCount) {
return passageCount.id === dbWorkbookQuestion.Question.PassageId;
});
if (passageIndex !== -1) {
return passageCounts[passageIndex].count++;
}
});
}).then(function() {
passageCounts = _.sortBy(passageCounts, 'count');
return passageCounts;
});
};
and I want to unit test it (I think). I instrumented mocha to do the testing, but my test doesn't seem all that.. thorough:
describe('Finding the least used Passage', function() {
it('should have a function called findLeastUsedPassage', function() {
return expect(WorkbookLib.findLeastUsedPassage).to.exist;
});
return it('should return the least used passages for a student', function() {
return WorkbookLib.findLeastUsedPassage(10).then(function(passageCounts) {
var passageCountsLength;
passageCountsLength = passageCounts.length;
expect(passageCountsLength).to.equal(74);
expect(passageCounts[0].count).to.be.at.most(passageCounts[1].count);
expect(passageCounts[1].count).to.be.at.most(passageCounts[5].count);
expect(passageCounts[56].count).to.be.at.most(passageCounts[70].count);
return expect(passageCounts[70].count).to.be.at.most(passageCounts[73].count);
});
});
});
What's the right approach to unit testing something like this?
This is a great resource for understanding how to break up your code to able to test it.
Currently, you're code can't be tested well because the logic is all intermingled between multiple database calls, business logic, and glue code. What you need to do is break it all out into multiple named functions that each do one thing, like you do now. Expect that instead of creating the functions in the chain you should create them outside of the chain, then just call them in the promise chain.
var passageCounts = [];
function findAllActivePassages() {
passageCounts = [];
return db.Passage.findAll({
where: {
active: true
}
})
}
function countPassages(dbPassage) {
var passage;
passage = dbPassage.get();
passage.count = 0;
return passageCounts.push(passage);
}
function findAllSubjects(StudentId) {
return db.Workbook.findAll({
where: {
SubjectId: 1,
gradedAt: {
$ne: null
},
StudentId: StudentId
},
include: [
{
model: db.WorkbookQuestion,
include: [db.Question]
}
],
limit: 10,
order: [['gradedAt', 'DESC']]
});
})
// ...
findAllActivePassages()
.each(countPassages)
.then(function() {
return findAllSubjects(studentId)
})
// ...
Now you can test each function individually and in isolation to ensure that they do what you expect
So for starters, you probably want to break up your promise chains to make the discrete units of your code more apparent. I did some quick psuedo javascript (most familliar w/ node so apologies if this doesn't fit vanilla javascript as cleanly).
var p1 = db.Passage.findAll({ where: { active: true }})
var p2 = db.Workbook.findAll({
where: {
SubjectId: 1,
gradedAt: {
$ne: null
},
StudentId: StudentId
},
include: [
{
model: db.WorkbookQuestion,
include: [db.Question]
}
],
limit: 10,
order: [['gradedAt', 'DESC']]
});
Promise.all([p1, p2])
.then(function(results){
var passages = results[0]
var workbooks = results[1];
var passageCounts = {};
passages.foreach(function(passage){
passagecounts[passage.get().id] = 0
});
workbooks.foreach(function(workbook){
workbook.workBookQuestions.foreach(function(question){
return passageCounts[dbWorkbookQuestion.Question.PassageId] += 1;
})
});
return Promise.resolve(passageCounts)
}).then(function(passageCounts){
passageCounts = _.sortBy(passageCounts, 'count'); //this has to change but don't know what underscore offers for sorting an object used as a hashmap
return passageCounts;
});
Now as far as unit testing - you're looking to test discrete units of it so the following use cases seem reasonable:
Do I get any result back when expected?
If i give it specific values are they sorted in the way I expect?
If I have no results for either query does it break? Should it?
It may behoove you to break out the DB calls from the logic and pass the results into a method, makes testing some of the scenarios a bit easier.

nodejs, mysql, async itterative functions

I'm having a bit of trouble with an itterative function in nodejs.
I'm stepping through an object and checking if that object has any sub-objects attached (think: a star has a planet has a moon has an orbital station has a ship).
I'm trying to assemble this all into a nice array of objects to push to the client.
Here's the function:
var subNodeProc = function(nodeList,sqlP,itteration_count) {
var async = require('async');
--itteration_count;
async.each(nodeList,function(dd,cb){
var simple = {
sql:sqlP,
values:[dd.node_id],
timeout:40000
};
dd.subnodes = false;
connection.query(simple, function(err,rslt){
if (err) {
cb(err);
} else {
if (rslt.length > 0) {
var r = nodeList.indexOf(dd);
if (itteration_count > 0) {
rslt = subNodeProc(rslt,sqlP,itteration_count);
}
nodeList[r].subnodes = rslt;
}
cb();
}
});
},function(err){
if (err) {
return err;
} else {
return nodeList;
}
});
}
When I trigger the function it returns a nodelist of undefined. Can anyone give me a pointer in the right direction? I can't get it to work
Thanks!
Edit: here's a sample of the data I'm itterating over:
The SQL statement:
SELECT n.id as node_id, n.name, n.system_id, n.parent_id as parent_id FROM nodes as n WHERE n.parent_id = ?
Sample nodeList for input:
[ { node_id: 1,
name: 'Planet A',
system_id: 1,
parent_id: null,
},
{ node_id: 2,
name: 'Moon',
system_id: 1,
parent_id: 1,
},
{ node_id: 3,
name: 'Debris',
system_id: 1,
parent_id: 2,
},
{ node_id: 4,
name: 'Asteroid',
system_id: 1,
parent_id: 1,
} ]
Moon A has a parent_id of 1 and node_id of 2, moon A also has a ship (ship A, node_id:3, parent_id:2) orbiting it.
What I want :
[ { node_id: 1,
name: 'Planet A',
system_id: 1,
parent_id: null,
subnodes:[{
node_id: 2,
name: 'Moon A',
system_id: 1,
parent_id: 1,
subnodes: [{
node_id:3,
name: 'Ship A',
system_id:1,
parent_id:2
},
{...}]
},
{...}]
},
{...}]
It's hard to tell whether there are any other major issues because I cannot see the data which you are feeding the method. However, there is one major problem with this: you're attempting to return data from a method that uses asynchronous method calls.
The asynchronous way is to return values via a callback. In your code, the very last function in your example (the callback) is called from a completely different scope (from within the async framework) so your nodeList or err is being lost in a scope you don't control.
You need to rethink your code so that the returned data is passed to a callback. You could leverage the async callback for this. Ad a callback argument to your subNodeProc method. Then you can call that callback, after async has finished, passing it the nodeList:
var subNodeProc = function (nodeList, sqlP, itteration_count, cb) {
var async = require('async');
--itteration_count;
async.each(nodeList,function(dd, cb){
var simple = {
sql:sqlP,
values:[dd.node_id],
timeout:40000
};
dd.subnodes = false;
connection.query(simple, function(err, rslt){
if (err) {
cb(err);
} else {
if (rslt.length > 0) {
var r = nodeList.indexOf(dd);
if (itteration_count > 0) {
rslt = subNodeProc(rslt,sqlP,itteration_count);
}
nodeList[r].subnodes = rslt;
}
cb();
}
});
}, function (err) {
if (err)
throw err;
else
cb(nodeList);
});
}
You would then use the method like this:
subNodeProc(nodeList, sqlP, itteration_count, function (processed) {
console.log(processed);
/* do whatever you want afterwards here */
});
Okay, the solution was pretty obvious, once I sussed it out. Much thanks to #shennan for getting me going.
The key is:
as #shennan mentioned, you don't work with returns, as we're working asynchronously. This means callbacks
and
You have to trigger callbacks for each part of the function. This is not possible with just one function, so to get the objects returning you need two, each doing different parts of the original function.
Here's what I've come up with. Hope someone can look it over and give me an opinion...
// Main processing function.
var subNodeProc = function(nodeList,sqlP,itteration_count,cback) {
var async = require('async');
itteration_count--;
async.each(nodeList,function(dd,cb){
if (itteration_count > 0) {
// Trigger SQL Walker subNodeProcWalker with the necessary data (dd.node_id in this case, with a callback)
subNodeProcWalker(dd.node_id,sqlP,itteration_count,function(nl) {
// Hey look! the walker has done its business. Time to fill the subnode and tell async we're done with this array node.
dd.subnodes = nl;
cb();
});
}
},function(){
// At the end of the run, return the nodelist intact.
cback(nodeList);
});
}
// SQL Walker with callback for subNodeProc
var subNodeProcWalker = function(node_id,sqlP,itteration_count,cback){
// assemble the object for the query and do the query
var simple = {
sql:sqlP,
values:[node_id],
timeout:40000
};
connection.query(simple, function(err,rslt){
if (err) {
console.log('Error in Query');
console.log(simple);
console.log(err);
cback(false);
} else {
// no error and a result? Quick! Trigger subNodeProc again
if (rslt.length > 0) {
subNodeProc(rslt,sqlP,itteration_count,function(nodePol) {
// Lookie lookie! A result from subNodeProc! There's life there! Quick! tell this function we're done!
cback(nodePol);
});
} else {
cback(false);
}
}
});
}

Categories