Teardown Errors when testing with Jest, Supertest, MongoDB, and Express - node.js

I'm testing my Express routes with Jest and Supertest. While setting up the app, I'm also connecting to MongoDB and adding the MongoClient to app.locals.
I'm getting an error which doesn't happen when I comment out my call to MongoClient.
ReferenceError: You are trying to `import` a file after the Jest environment has been torn down.
at BufferList.Readable (server/node_modules/readable-stream/lib/_stream_readable.js:179:22)
at BufferList.Duplex (server/node_modules/readable-stream/lib/_stream_duplex.js:67:12)
at new BufferList (server/node_modules/bl/bl.js:33:16)
at new MessageStream (server/node_modules/mongodb/lib/cmap/message_stream.js:35:21)
at new Connection (server/node_modules/mongodb/lib/cmap/connection.js:54:28)
/Users/zackchan/Documents/dev/server/node_modules/readable-stream/lib/_stream_readable.js:111
var isDuplex = stream instanceof Duplex;
^
TypeError: Right-hand side of 'instanceof' is not callable
at new ReadableState (/Users/zackchan/Documents/dev/server/node_modules/readable-stream/lib/_stream_readable.js:111:25)
at BufferList.Readable (/Users/zackchan/Documents/dev/server/node_modules/readable-stream/lib/_stream_readable.js:183:25)
at BufferList.Duplex (/Users/zackchan/Documents/dev/server/node_modules/readable-stream/lib/_stream_duplex.js:67:12)
at new BufferList (/Users/zackchan/Documents/dev/server/node_modules/bl/bl.js:33:16)
at new MessageStream (/Users/zackchan/Documents/dev/server/node_modules/mongodb/lib/cmap/message_stream.js:35:21)
at new Connection (/Users/zackchan/Documents/dev/server/node_modules/mongodb/lib/cmap/connection.js:54:28)
at /Users/zackchan/Documents/dev/server/node_modules/mongodb/lib/core/connection/connect.js:36:29
at callback (/Users/zackchan/Documents/dev/server/node_modules/mongodb/lib/core/connection/connect.js:280:5)
at TLSSocket.connectHandler (/Users/zackchan/Documents/dev/server/node_modules/mongodb/lib/core/connection/connect.js:325:5)
at Object.onceWrapper (events.js:421:28)
When I comment out my MongoClient call, I get this Jest warning:
Jest did not exit one second after the test run has completed.
This usually means that there are asynchronous operations that weren't stopped in your tests. Consider running Jest with `--detectOpenHandles` to troubleshoot this issue.
Here's my test script and app module
app.js
const https = require('https');
const fs = require('fs');
const path = require('path');
const dotenv = require('dotenv');
const express = require('express');
const rateLimiter = require('express-rate-limit');
const { MongoClient } = require('mongodb');
const app = express();
const port = process.env.PORT || 443;
const limit = rateLimiter({ window: 15 * 60 * 1000, max: 100 });
var httpsOptions;
if(process.env.NODE_ENV === 'development'){
const rootCA = require('ssl-root-cas').create().addFile(path.join(__dirname, './cert/CA.pem'));
https.globalAgent.options.ca = rootCA;
httpsOptions = {
key: fs.readFileSync(path.join(__dirname, './cert/localhost.key')),
cert: fs.readFileSync(path.join(__dirname, './cert/localhost.crt'))
};
}
MongoClient.connect(process.env.MONGODB_URI, { useNewUrlParser: true, useUnifiedTopology: true }, (err, mongoClient) => {
if(err) throw err;
app.locals.mongoClient = mongoClient;
});
app.use(limit);
app.use(express.json());
app.get('/', (req, res) => {
res.json({path: '/'});
});
const server = https.createServer(httpsOptions, app).listen(port);
module.exports = app;
test.js
const dotenv = require('dotenv');
const path = require('path');
process.env = dotenv.config({path: path.resolve(__dirname, '.env')}).parsed;
const request = require('supertest');
const app = require('../app');
describe('GET /', () => {
it('responds with path of /', (done) => {
// app.locals is empty here
request(app).get('/').expect(JSON.stringify({path: '/'}), done);
});
});
I've tried closing the connection to MongoDB after the test case using app.locals.mongoClient.close() but mongoClient is undefined here. I've also tried wrapping the MongoClient.connect() call in an async function then calling it but that doesn't help either.
Anyone have thoughts on what I should try?

I fixed this by:
Following instructions from Jest docs. You can still access the MongoDB connection through req.app.locals if you assign it to app.locals in the test.js file.
Wrapped my MongoClient.connect() call in app.js:
if(process.env.NODE_ENV === 'production'){
MongoClient.connect(...)
}

Related

Why does exporting the socket.io server as a constant using module.exports? make it undefined?

I am runnnig a socketserver in express but I am unable to export the io constant to a different file without it becoming undefined. I, therefore, cannot use io.on( ), it gives a type error. When i tried to console.log it, its undefined. Please help
server.js:
const app = require("express")();
const httpServer = require("http").createServer(app);
const io = require("socket.io")(httpServer, options);
// const io = new Server(server, options)
io.on("connection", socket => { console.log('hello') });
...
module.exports = {httpServer, app, io};
router.js:
const {io} = require("../server.js");
// console.log(io)

Nodejs TypeError "is not a constructor"

I am working through the course "Build a Blockchain and Cryptocurrency From Scratch from Udemy. I have no prior experience with NodeJS I have been following along with the instructor's code, but recently hit a snag that I cannot resolve. When trying to build the project to run test scripts via npm run dev, I get the following error:
/home/OnlyDean/Workspace/udemy_blockchain_course_1/app/index.js:13
const p2pServer = new P2pServer(bc);
^
TypeError: P2pServer is not a constructor
Answers I've found online seem to indicate that I'm calling an instance of the class P2pServer rather than the constructor of the class itself. This is borne out by the output of console.log(P2pServer);, which returns [Object object]. I have scoured the code for errors, and compared my code to what the instructor has on the screen. I can't seem to find any differences. Below are my index.js and p2p-server.js classes, which I think are the only relevant files.
index.js
const express = require('express');
const bodyParser = require('body-parser');
const Blockchain = require('../blockchain');
const P2pServer = require('./p2p-server');
const HTTP_PORT = process.env.HTTP_PORT || 3001;
const app = express();
const bc = new Blockchain();
//console.log(`Blockchain = ${Blockchain}`)
console.log(`P2pServer = ${P2pServer}`)
console.log(bc)
const p2pServer = new P2pServer(bc);
app.use(bodyParser.json());
app.get('/blocks', (req, res) => {
res.json(bc.chain);
})
app.post('/mine', (req, res) => {
const block = bc.addBlock(req.body.data);
console.log(`New block added: ${block.toString()}`);
res.redirect('/blocks');
});
app.listen(HTTP_PORT, () => console.log(`Listening on port ${HTTP_PORT}`));
p2pServer.listen();
p2p-server.js
const Websocket = require('ws');
const P2P_PORT = process.env.P2P_PORT || 5001;
const peers = process.env.PEERS ? process.env.PEERS.split(',') : [];
class P2pServer {
constructor(blockchain) {
this.blockchain = blockchain;
this.sockets = [];
}
listen() {
const server = new Websocket.Server({ port: P2P_PORT});
server.on('connection', socket => this.connectSocket(socket));
this.connectToPeers();
console.log(`Listening for peer-to-peer connections on: ${P2P_PORT}`);
}
connectToPeers() {
peers.forEach(peer => {
const socket = new Websocket(peer);
socket.on('open', () => this.connectSocket(socket));
});
}
connectSocket(socket) {
this.sockets.push(socket);
console.log(`Socket connected`);
}
}
module.export = P2pServer;
Any help resolving this error would be greatly appreciated -- I'm kind of stuck with the course until I can resolve this. Thanks!
The problem is at the export of the P2pServer class
should be like this (exports and not export):
module.exports = P2pServer;

NodeJS Mongoose handle database connection failed exception

I'm building a restful web service api using NodeJS.It uses Mongoose as ODM and using MongoDB for backend.
Below i will explain my scenario
I started nodejs server
After that i shutdown the MongoDB database.
Then call the GET api call,it doest catch any errors and api call get hang.
database config in main.js file
var mongoose = require('mongoose');
var uri = 'mongodb://localhost/mydb';
mongoose.Promise = global.Promise;
var options = { server: {socketOptions: { keepAlive: 300000, connectTimeoutMS: 10000 } } } ;
mongoose.connect(uri,options);
var db = mongoose.connection;
db.on('error',console.log.bind(console,'connection refused !!!!!'));
db.once('open', console.log.bind(console,'connection success !!!!!'));
this is my basic GET call
var express = require('express');
var router = express.Router();
var mongoose = require('mongoose');
var rootRes = require('../model/rootresources.js');
router.get('/', function(req, res, next) {
rootRes.find({},function (err, rootResource) {
if (err){
console.log('Error occurd !!!') }
res.json(rootResource);
});
});
Even database connection failed, the code does not goes to error block. So didn't capture the database refuse when database connection is failed in the API call.
I want to capture that error and send internal server error (code:500) to client. I tried to find the solution but still could not find it
Any solutions or do i made a mistake ?
Thank you
Amila
Did you put the two parts of code in the same file(ie. main.js) or two different files.
put them in the same file, and run node main.js do throw exceptions.
// main.js
var mongoose = require('mongoose');
var uri = 'mongodb://localhost/mydb';
mongoose.Promise = global.Promise;
var options = { server: {socketOptions: { keepAlive: 300000,
connectTimeoutMS: 10000 } } } ;
mongoose.connect(uri,options);
var db = mongoose.connection;
db.on('error',console.log.bind(console,'connection refused !!!!!'));
db.once('open', console.log.bind(console,'connection success !!!!!'));
var express = require('express');
var router = express.Router();
var mongoose = require('mongoose');
var rootRes = require('../model/rootresources.js');
router.get('/', function(req, res, next) {
rootRes.find({},function (err, rootResource) {
if (err){
console.log('Error occurd !!!') }
res.json(rootResource);
});
});
exceptions are:
connection refused !!!!! { MongoError: failed to connect to server [localhost:27017] on first connect [MongoError: connect ECONNREFUSED 127.0.0.1:27017]
etc...
So, I think maybe you put codes about express in a file like index.js and codes about mongoose connection in another file. And just run node index.js in command line. While running codes in index.js will not include codes in other files, codes in main.js will not be executed. As the result, there is no error info.
Updates
Two ways of I know two ways of doing this:
1.In main.js create function which creates connection to database and returns a instance of db so that you can call it function in you main code.
// main.js like this
var mongoose = require('mongoose');
function createConnection(url) {
mongoose.connect(url,options);
var db = mongoose.connection;
db.on('error',console.log.bind(console,'refused !!!!!'));
db.once('open', console.log.bind(console,'success !!!!!'));
return db;
}
// export function
module.exports = createConnection;
// in your index.js
var createConnection = require('./main.js');
var db = createConnection(url);
// other codes here
2.Using require or vm to compile and run javascipt code. You can find vm api detail here
//main.js
var mongoose = require('mongoose');
var uri = 'mongodb://localhost/mydb';
mongoose.Promise = global.Promise;
var options = { server: {socketOptions: { keepAlive: 300000,
connectTimeoutMS: 10000 } } } ;
mongoose.connect(uri,options);
var db = mongoose.connection;
db.on('error',console.log.bind(console,'connection refused !!!!!'));
db.once('open', console.log.bind(console,'connection success !!!!!'));
// index.js
// require will load file and execute automaticly
var scriptSrc = require('./main');
// other codes here
You can think of the second way as using eval('var mongoose = require('mongoose');
var uri = 'mongodb://localhost/mydb'; etc...)
mongoose connection do not happen unless you hit a request. so its best you handle it in your first request middleware. see code insight bellow.
module.exports = function () {
return function (req, res, next) {
mongoose.connect(URL, MONGO_OPTIONS);
mongoose.connection
.once('open', () => { })
.on('error', (error) => {
res.status(401).json({});
});
...
Then pass the middleware above to your router: let me know if you need more explanation
router.get('/', myMiddleware(){})

node.js server and HTTP/2 (2.0) with express.js

Is it possible currently to get node.js HTTP/2 (HTTP 2.0) server? And http 2.0 version of express.js?
var express = require('express');
var app = express();
app.get('/', function (req, res) {
res.send('hello, http2!');
});
var options = {
key: fs.readFileSync('./example/localhost.key'),
cert: fs.readFileSync('./example/localhost.crt')
};
require('http2').createServer(options, app).listen(8080);
EDIT
This code snippet was taken from a conversation on Github.
If you are using express#^5 and http2#^3.3.4, then the correct way to start the server is:
const http2 = require('http2');
const express = require('express');
const app = express();
// app.use('/', ..);
http2
.raw
.createServer(app)
.listen(8000, (err) => {
if (err) {
throw new Error(err);
}
/* eslint-disable no-console */
console.log('Listening on port: ' + argv.port + '.');
/* eslint-enable no-console */
});
Notice the https2.raw. This is required if you want to accept TCP connections.
Note that at the time of this writing (2016 05 06), none of the major browsers support HTTP2 over TCP.
If you want to accept TCP and TLS connections, then you need to start the server using the default createServer method:
const http2 = require('http2');
const express = require('express');
const fs = require('fs');
const app = express();
// app.use('/', ..);
http2
.createServer({
key: fs.readFileSync('./localhost.key'),
cert: fs.readFileSync('./localhost.crt')
}, app)
.listen(8000, (err) => {
if (err) {
throw new Error(err);
}
/* eslint-disable no-console */
console.log('Listening on port: ' + argv.port + '.');
/* eslint-enable no-console */
});
Note that at the time of this writing, I did manage to make express and http2 to work (see https://github.com/molnarg/node-http2/issues/100#issuecomment-217417055). However, I have managed to get http2 (and SPDY) to work using spdy package.
const spdy = require('spdy');
const express = require('express');
const path = require('path');
const fs = require('fs');
const app = express();
app.get('/', (req, res) => {
res.json({foo: 'test'});
});
spdy
.createServer({
key: fs.readFileSync(path.resolve(__dirname, './localhost.key')),
cert: fs.readFileSync(path.resolve(__dirname, './localhost.crt'))
}, app)
.listen(8000, (err) => {
if (err) {
throw new Error(err);
}
/* eslint-disable no-console */
console.log('Listening on port: ' + argv.port + '.');
/* eslint-enable no-console */
});
There is an open pr for express 5.0 since 2018, https://github.com/expressjs/express/pull/3730. Until that is merged, it won't work out of the box.
I have created the solution in the form of a package, https://www.npmjs.com/package/http2-express-bridge
const express = require('express')
const http2Express = require('http2-express-bridge')
const http2 = require('http2')
const { readFileSync } = require('fs')
// Use the wrapper function that returns the application
const app = http2Express(express)
const options = {
key: readFileSync('<Certificate Key>'),
cert: readFileSync('<Certificate file>'),
allowHTTP1: true
};
app.get('/', function (req, res) {
res.send('Hello World')
})
const server = http2.createSecureServer(options, app)
server.listen(3000, () => {
console.log(`listening on port 3000`)
})
This works, and it falls back to Http/1.1 when it receives an Http/1.1 request.
I have also included 'res.push' method for ease of server push. The package works with ESModules and Typescript.
This issue is still around today (2016 as of writing this), so I decided to have a go at making a workaround to make express and http2 packages work nicely together: https://www.npmjs.com/package/express-http2-workaround
Edit: Does not work on any NodeJS version above v8.4 due to the native 'http2' module.
Install via NPM: npm install express-http2-workaround --save
// Require Modules
var fs = require('fs');
var express = require('express');
var http = require('http');
var http2 = require('http2');
// Create Express Application
var app = express();
// Make HTTP2 work with Express (this must be before any other middleware)
require('express-http2-workaround')({ express:express, http2:http2, app:app });
// Setup HTTP/1.x Server
var httpServer = http.Server(app);
httpServer.listen(80,function(){
console.log("Express HTTP/1 server started");
});
// Setup HTTP/2 Server
var httpsOptions = {
'key' : fs.readFileSync(__dirname + '/keys/ssl.key'),
'cert' : fs.readFileSync(__dirname + '/keys/ssl.crt'),
'ca' : fs.readFileSync(__dirname + '/keys/ssl.crt')
};
var http2Server = http2.createServer(httpsOptions,app);
http2Server.listen(443,function(){
console.log("Express HTTP/2 server started");
});
// Serve some content
app.get('/', function(req,res){
res.send('Hello World! Via HTTP '+req.httpVersion);
});
The above code is a working express application that uses both the nodejs http module (for HTTP/1.x) and the http2 module (for HTTP/2).
As mentioned in the readme, this creates new express request and response objects and sets their prototypes to http2's IncomingMessage and ServerResponse objects. By default, it's the inbuilt nodejs http IncomingMessage and ServerResponse objects.
I hope this helps :)

Node/Express.js mocha setup not working

Here is my test file:
var request = require('superagent'),
expect = require('expect'),
sinon = require('sinon'),
app = require("../../../../app");
request = request(app);
describe("HealthCheck", function () {
describe("/health_check", function () {
it("should return a 200 status", function (done) {
request
.get("http://localhost:3000/health_check")
.end(function(err, res) {
expect(res).to.exist;
expect(res.status).to.equal(200);
expect(res.body).to.contain('OK');
return done();
});
});
});
})
The error I keep running into is
1) HealthCheck /health_check should return a 200 status:
TypeError: Cannot call method 'end' of undefined
Can't figure out what I am doing wrong
Here is my app.js:
require('coffee-script/register');
var express = require('express'),
config = require('./config/config'),
fs = require('fs');
var app = express();
require('./config/express')(app, config);
app.listen(config.port);
exports.app = app;
You're exporting app as app
Therefore, when you require the module, you'll need to require the name of the function you're exporting - in this case 'app'
So, you can either change your test
var request = require('superagent'),
expect = require('expect'),
sinon = require('sinon'),
app = require("../../../../app");
request = request(app.app);
Or change your app.js to exports a single function:
Change
exports.app = app;
to
module.exports = app;
There's a good article on this here: http://openmymind.net/2012/2/3/Node-Require-and-Exports/

Resources