NodeJS and ExpressJS app times out with TAP - node.js

I'm trying to test an ExpressJS (4.17.8) and NodeJS (16.3) powered server (app) with tap, and later with supertest. First I'm testing the instantiation of the server, and later its routes.
For this, my app is wrapped in a Connector Class that:
has an ExpressJS server (app)
connects to an external system
registers endpoints
has a method startup that calls app.listen
So I have a test file like this:
import test, { Test } from "tape-promise/tape";
test("connects to X", async (t: Test) => {
connector = new Connector();
await connector.ConnectToExternalSystem(); // connects to external system
await connector.registerEndpoints(); // e.g., sets to the Express app: app[get](/endpoint)...
await connector.listen(); // gets stuck?
t.ok(connector);
t.end();
My problem is that for every test I perform, tap seems to get stuck (happens with Jest as well) in connector.listen() - leading for the test to timeout.
My project and tests are written in Typescript 4.3.5. I am using the following npm script to run the tests:
"test": "tap --ts --jobs=1 --node-arg=--max-old-space-size=4096 --timeout=15 --branches=45 --functions=70 --lines=75 --statements=75 \"src/test/{unit,integration}/\"",
Is there anything I'm doing wrong? Appreciate your advice on this.
Thanks.

Depends on what the implementation of Connector really looks like. Assuming that the .listen() method of it calls express' listen under the hood the issue might be that you are not handling the success callback or that it's not wired up properly to the returned promise via the resolve callback of the promise.
So something like this could work (rough pseudo code, not tested):
class Connector {
listen(): Promise<void> {
return new Promise((resolve, reject) => {
this.expressApp.listen((err: Error) => {
if (err) {
reject(err);
} else {
resolve();
}
});
});
}
}
What the above does is ensures that the returned promise resolves once the callback has been invoked (or rejects if the callback was passed in an error which is the standard NodeJS error handling style)

Related

NodeJS Asyn/Await for build configuration with nodecloud-config-client

My NodeJS application is using spring cloud server for configurations and I am loading this at the application startup. I am using the below function to load config but I can see the promise status is pending and seems to be the call is still async. How can I make it as a sync call to wait until the configurations are loaded. Thanks in advance!
async function fetchConfig(){
await client.getConfig().then(conf => {
console.log(conf);
}).catch(console.error);
}
async/await is a way to replace promise based callback, but you use them at same time, you can change it to:
async function fetchConfig(){
try {
const conf = await client.getConfig();
console.log(conf);
} catch (ex) {
console.error(ex);
}
}

Mongodb testing in Node

I have been using Mocha for testing in Node because it seems to be what most people use. I am also using MongoDB to store my data and since my server is a simple API server pretty much all of my methods are just plain database queries which I am trying to test with Mocha.
Now the Issue that I am facing is that (besides the fact that it seems to be quite hard to test async functions in general) I can not seem to find a proper way to test for a mongoDB excpetion.
it('Should not create account', async (done) => {
try {
await createAccountDB(user);
await createAccountDB(user);
} catch (err) {
assert.notEqual(err, null);
console.log(err);
}
finally {
done();
}
});
});
What I am trying here is to create an account for a User (Basically just saving the Object to the db) and then creating the same account again which should result in a duplicate key error.
Now, this doesn't work and as far as I can tell that is because i have defined both async and done. The reason why I did this is, if I don't define async I would need to have a whole bunch of .then and .catches which would make the code look horrible but if I don't include then done() in the finally block, my test seems to never even make it to the catch block.
Is there any way to write tests like these in Mocha which don't make your code look absouletly horrible?
Since you're already using the async/await model you don't necessarily need the done callback for the test case. Certain versions of mocha will warn you when you have more than one means of indicating test completion. Try this:
it('should not create an account', async function() {
try {
await createAccountDB(user);
await createAccountDB(user);
throw new Error('Did not reject a duplicate account');
} catch (err) {
assert.notEqual(err, null);
// any other assertions here
}
});
The error thrown in the try/catch block is very important - without it, the test will still pass even if the error is not thrown.

How do I go "async all the way down" with Node Express?

I have many async functions in my system, so I need to go "async all the way down", which is to the point where the http.Server and express.Application app are created.
(This is unavoidable in an async system - there will be many async routines which are needed in constructors, which cannot be done, and so we need to use async factory functions instead, which lead to async creep all the way down to the entry point.)
But I'm not sure of the Node/TypeScript syntax to use to bootstrap the app.
My main entry point is System.ts:
class default export System {
public constructor() {
// init Express.Application
// init http.Server
// init other parts of the system
}
public async start(): Promise<void> {
// start the system asynchronously
// start listening with http.Server
}
}
Then I have a bootstrapping module Main.ts:
import System from "./System"
const system = new System();
export default ???; // PROBLEM IS HERE
Which should be run:
node ./dist/Main.js
But I'm not sure what to use in the export line. I tried all these:
export default await system.start(); // doesn't compile (obviously)
export default system.start(); // doesn't seem right
export default system.start().then(); // this works *maybe*
The last line works based on a smoke test - but I'm not sure if that's the way to do it, and whether there's something down the line that may fail.
What is the canonical way to start an asynchronous node app?
UPDATE
Based on #JacobGillespie's answer, the Main.ts bootstrapping module is now:
import System from "./System"
new System().start().then();
//new System().start().catch(e => console.error(e)); // alternative
In my case, System.ts has handlers for errors and unhandled promises, and does logging (otherwise use the "alternative" line). So the bootstrapping module just bootstraps the system.
async / await here are operating on promises, so you essentially want to "start" the promise by calling .then or .catch.
My go-to snippet for this is creating an async run or main function, then attaching error handling to the process, something like this:
async function run() {
// run the app, you can await stuff in here
}
run().catch(err => {
console.error(err.stack)
process.exit(1)
})
In your case that would look like (Main.ts):
import System from "./System"
async function run() {
const system = new System()
await system.start()
}
run().catch(err => {
console.error(err.stack)
process.exit(1)
})
You don't need to export anything since this module file isn't being imported anywhere else (it's the entry file).
You can just call system.then() or system.catch(), but personally I like the async function run() pattern since you may need to coordinate more than one async thing in the future and this makes the code more explicit.
system.start().then() => {
value => export default value
}
In my opinion, a better way would be:
System.ts:
function System():Promise<string>{
//setup express and the server
return new Promise((res,rej) => {
//the server var is just the http server instance
server.listen(8000,() => resolve("server created"));
});
}
export {System}
And then in Main.ts:
import {System} from "yourpath"
And then:
System().then(() => {
//code runs when server is created
}).catch(err => console.error(err));

Calling http GET requests from ibm cloud functions using nodejs

while developing the cloud function I got the following error.
i created a cloud action and used that action name inside the json response in watson assistance. then once the intent is matched it call the cloud function. I selected nodejs as developing language of the cloud function.
now my real requirement is to call a https get request from that cloud function. for that i need to use nodejs library called node-rest-client.
normally we need to install it by typing "npm install node-rest-client" in the terminal. since there is no terminal in IBM nodejs editor i got, could you please let me know how to do that.
As a substitute i used https library which is already in nodejs package. and wrote the following code in the editor
const https = require('https');
function main() {
https.get('https://f6054382.ngrok.io/webhook/testRequest', (resp) => {
resp.on('data', (d) => {
process.stdout.write(d);
});
});
}
main();
once I type "node init" in local folder and add the above code to index.js file and then run it using the command "node index.js" code works successfully by giving me the expected output from the webservice.
But I do not get that result in IBM nodejs editor
https://console.bluemix.net/openwhisk/details/action/dialogif.psi%2540gmail.com_dev/webhookCall/code
once i save the above code and invoke by clicking the button in right upper i get the successes response in green as follows.
Activation ID:
341d4e5bc81f4e489d4e5bc81f2e4888
Results:
{}
Logs:
[]
could you please help me to sort this out
Thank you
When executing an asynchronous operation, e.g. HTTP request, you need to return a Promise from the action handler. This ensures the platform will block on that asynchronous result before completing the invocation.
function main() {
return new Promise((resolve, reject) => {
https.get('https://f6054382.ngrok.io/webhook/testRequest', (resp) => {
resp.on('data', (d) => {
process.stdout.write(d);
resolve({})
});
});
})
}
The underlying issue is that your code runs asynchronously and does not return a Promise to indicate a result will be available in the future.
I'd suggest to use the request-promises, which comes pre-packaged with OpenWhisk's Node.js runtime.
const request = require('request-promise');
function main(params) {
return request("https://f6054382.ngrok.io/webhook/testRequest").then(response => {
process.stdout.write(response);
});
}

How to return promise to the router callback in NodeJS/ExpressJS

I am new to nodejs/expressjs and mongodb. I am trying to create an API that exposes data to my mobile app that I am trying to build using Ionic framework.
I have a route setup like this
router.get('/api/jobs', (req, res) => {
JobModel.getAllJobsAsync().then((jobs) => res.json(jobs)); //IS THIS THe CORRECT WAY?
});
I have a function in my model that reads data from Mongodb. I am using the Bluebird promise library to convert my model functions to return promises.
const JobModel = Promise.promisifyAll(require('../models/Job'));
My function in the model
static getAllJobs(cb) {
MongoClient.connectAsync(utils.getConnectionString()).then((db) => {
const jobs = db.collection('jobs');
jobs.find().toArray((err, jobs) => {
if(err) {
return cb(err);
}
return cb(null, jobs);
});
});
}
The promisifyAll(myModule) converts this function to return a promise.
What I am not sure is,
If this is the correct approach for returning data to the route callback function from my model?
Is this efficient?
Using promisifyAll is slow? Since it loops through all functions in the module and creates a copy of the function with Async as suffix that now returns a promise. When does it actually run? This is a more generic question related to node require statements. See next point.
When do all require statements run? When I start the nodejs server? Or when I make a call to the api?
Your basic structure is more-or-less correct, although your use of Promise.promisifyAll seems awkward to me. The basic issue for me (and it's not really a problem - your code looks like it will work) is that you're mixing and matching promise-based and callback-based asynchronous code. Which, as I said, should still work, but I would prefer to stick to one as much as possible.
If your model class is your code (and not some library written by someone else), you could easily rewrite it to use promises directly, instead of writing it for callbacks and then using Promise.promisifyAll to wrap it.
Here's how I would approach the getAllJobs method:
static getAllJobs() {
// connect to the Mongo server
return MongoClient.connectAsync(utils.getConnectionString())
// ...then do something with the collection
.then((db) => {
// get the collection of jobs
const jobs = db.collection('jobs');
// I'm not that familiar with Mongo - I'm going to assume that
// the call to `jobs.find().toArray()` is asynchronous and only
// available in the "callback flavored" form.
// returning a new Promise here (in the `then` block) allows you
// to add the results of the asynchronous call to the chain of
// `then` handlers. The promise will be resolved (or rejected)
// when the results of the `job().find().toArray()` method are
// known
return new Promise((resolve, reject) => {
jobs.find().toArray((err, jobs) => {
if(err) {
reject(err);
}
resolve(jobs);
});
});
});
}
This version of getAllJobs returns a promise which you can chain then and catch handlers to. For example:
JobModel.getAllJobs()
.then((jobs) => {
// this is the object passed into the `resolve` call in the callback
// above. Do something interesting with it, like
res.json(jobs);
})
.catch((err) => {
// this is the error passed into the call to `reject` above
});
Admittedly, this is very similar to the code you have above. The only difference is that I dispensed with the use of Promise.promisifyAll - if you're writing the code yourself & you want to use promises, then do it yourself.
One important note: it's a good idea to include a catch handler. If you don't, your error will be swallowed up and disappear, and you'll be left wondering why your code is not working. Even if you don't think you'll need it, just write a catch handler that dumps it to console.log. You'll be glad you did!

Resources