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.
Related
I have created a new web app that has 10 pages / forms and during the load of those pages 6 of the pages / forms call the same JavaScript methods and 4 call the same plus some additional methods. I can obviously call the the methods individually in the page but I'm wondering how I can do this in a more intelligent manner.
Currently all I'm doing is calling the methods at the bottom of the page like:
<script>
somemethod1(someparam1);
somemethod2(someparam1, someparam2);
somemethod3();
somemethod4();
somemethod5(someparam1);
</script>
It would be nicer to call something like:
<script>
Execute('somemethod1', 'somemethod2''somemethod3', 'somemethod4', 'somemethod5')();
</script>
I don't think its a good practice to do it. But sure, it'd be nicer to have Execute function like that if you don't need to pass any parameters.
You can do it like,
function test1() {
console.log('test1');
}
function test2() {
console.log('test2');
}
var Execute = function() {
for (var i = 0; i < arguments.length; i++) {
var funcName = arguments[i];
if (typeof window[funcName] == 'function') {
window[funcName]();
}
}
}
Execute('test1', 'test2')
However, as your question edited that you need to pass specific parameter/s to one or more of the functions, here's the intelligent way to do it.
test1(param1);
test2(param2, param1);
If you have uniform procedures and you need to call them in a certain order, like my example below for iteration nested arrays, then you could use the given proposal, which uses the following functions for the function before.
This might not work for other needs.
function Callback(array) {
this.array = array;
}
Object.defineProperties(Callback.prototype, {
start: {
get: function () {
return this.getCallback(0);
}
},
getCallback: {
value: function (i) {
var that = this;
return this.array[i].bind({ get next() { return that.getCallback(i + 1); } });
}
}
});
// example
function getSubmodel(model, submodel) {
var callback = new Callback([
function (a) { a.Categories.forEach(this.next); },
function (a) { a.forEach(this.next); },
function (a) { if (a.brandname === model) { a.models.forEach(this.next); } },
function (a) { if (a.name === submodel) { a.submodel.forEach(this.next); } },
function (a) { result.push(a.name); }
]),
result = [];
data.forEach(callback.start);
return result;
}
var data = [{
Storename: "Zig Zag Mobiles",
Shopid: "asdef1234",
Categories: [[{
models: [{
submodel: [{
price: null,
name: "Lumia 735 TS"
}, {
price: "3200",
name: "Lumia 510"
}], name: "Lumia"
}],
brandname: "Nokia",
}]]
}];
console.log(getSubmodel('Nokia', 'Lumia'));
.as-console-wrapper { max-height: 100% !important; top: 0; }
it would be nicer to call something like Execute('somemethod1',
'somemethod2''somemethod3', 'somemethod4', 'somemethod5')();
Provided that you do not need to pass in argument(s) to the individual method you can do this:
[func1, func2, func3].map(function(func){
func();
});
Otherwise the way to do it is to just call the methods with the required arguments as you are already doing now unless there is really significant benefits to be gained from creating an abstraction where you can pass in both the method to be called and its associated arguments.
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.
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);
}
}
});
}
I have the following code (Mongoose has been promisified with Bluebird)
function createNewCourse(courseInfo, teacherName) {
var newCourse = new courseModel({
cn: courseInfo.courseName,
cid: courseInfo.courseId
});
return newCourse.saveAsync()
.then(function (savedCourse) {
var newTeacher = new newTeacherModel({
na: teacherName,
_co: savedCourse._id // This would be an array
});
return newTeacher.saveAsync().then(function() {
return newCourse;
});
});
}
This is a simplification of my problem, but it illustrates it well. I want my createNewCourse function to return a promise that, once resolved, will return the newly saved course, not the teacher. The above code works, but it's not elegant and does not use promises well to avoid callback hell.
Another option I considered is returning the course and then doing a populate, but that would mean re-querying the database.
Any ideas how to simplify this?
Edit: I thought it might be useful to post the save code but using native callbacks (omitting error-handling)
function createNewCourse(courseInfo, teacherName, callback) {
var newCourse = new courseModel({
cn: courseInfo.courseName,
cid: courseInfo.courseId
});
newCourse.save(function(err, savedCourse) {
var newTeacher = new newTeacherModel({
na: teacherName,
_co: savedCourse._id // This would be an array
});
newTeacher.save(function(err, savedTeacher) {
callback(null, newCourse);
});
});
}
Use .return():
function createNewCourse(courseInfo, teacherName) {
var newCourse = new courseModel({
cn: courseInfo.courseName,
cid: courseInfo.courseId
});
return newCourse.saveAsync().then(function (savedCourse) {
var newTeacher = new newTeacherModel({
na: teacherName,
_co: savedCourse._id // This would be an array
});
return newTeacher.saveAsync();
}).return(newCourse);
}
Remember how chaining works?
.then(function() {
return somethingAsync().then(function(val) {
...
})
})
Is equivalent to (disregarding any closure variables):
.then(function() {
return somethingAsync()
})
.then(function(val) {
...
})
return(x) is simply equivalent to .then(function(){return x;})
I am having trouble with listsQuery not executing by the time everything gets sent to the browser. I know I need a Promise or something in there, but my attempts are so far unsuccessful. Help!
function processNavigation(navigation) {
var nav = [];
_.each(navigation, function(navItems) {
var navProperties = {
name: navItems.get("Name"),
longName: navItems.get("LongName"),
icon: navItems.get("Icon"),
url: navItems.get("Url"),
module: navItems.get("Module"),
runScript: navItems.get("RunScript"),
sortOrder: navItems.get("SortOrder")
};
switch (navItems.get("Module")) {
case "lists":
var listsQuery = new Parse.Query("ListItems"); // This should return back! But it's async? Needs promise?
listsQuery.ascending("SortOrder");
listsQuery.find().then(
function(results) {
var list = [];
_.each(results, function(listItems) {
var listProperties = {
name: listItems.get("Name"),
subName: listItems.get("Subname"),
sortOrder: listItems.get("SortOrder")
};
});
list.push(listProperties);
navProperties["source"] = list;
},
function() {
res.send('error');
}
);
break;
default:
navProperties["source"] = null;
break;
}
nav.push(navProperties);
});
res.send(nav);
}
This should give you something to go on, im not sure if it will work but it should show you the concept.
What you need to do is create an array of promises as it looks like your performing a query for each item in an array, and because the queries take some time your response is sent before the queries are complete. You then evaluate the array of promises and send your response back only when they are resolved.
I would suggest you split your logic into a few more functions as it's a little hard to follow.
Take a look at the parallel promises section
function processNavigation(navigation) {
var nav = [];
var promises = []
_.each(navigation, function(navItems) {
var navProperties = {
name: navItems.get("Name"),
longName: navItems.get("LongName"),
icon: navItems.get("Icon"),
url: navItems.get("Url"),
module: navItems.get("Module"),
runScript: navItems.get("RunScript"),
sortOrder: navItems.get("SortOrder")
};
switch (navItems.get("Module")) {
case "lists":
promises.push((function(navProperties){
var listsQuery = new Parse.Query("ListItems"); // This should return back! But it's async? Needs promise?
listsQuery.ascending("SortOrder");
listsQuery.find().then(
function(results) {
var list = [];
_.each(results, function(listItems) {
var listProperties = {
name: listItems.get("Name"),
subName: listItems.get("Subname"),
sortOrder: listItems.get("SortOrder")
};
});
list.push(listProperties);
navProperties["source"] = list;
promise.resolve();
},
function() {
promise.reject();
res.send('error');
}
);
})(navProperties))
break;
default:
navProperties["source"] = null;
break;
}
nav.push(navProperties);
});
Parse.Promise.when(promises).then(function(){
res.send(nav);
})
}