Jest not close()ing expressjs server when run on AWS Codebuild - node.js

When I run locally jest exits fine, but when run on codebuild jest will not exit and gives this error:
Jest has detected the following 1 open handle potentially keeping Jest from exiting:
● TCPSERVERWRAP
13 | routes(app)
14 |
> 15 | app.listen(port, async err => {
Both these variants work locally but not on codebuild:
afterAll((done) => {
if (app) {
app.close(done);
}
})
afterAll(async () => {
if (app) {
await app.close()
}
})
Using process.exit(0) does not help

Ok, bit of an amateur error. When I wrapped the app.close() call it turned out that it was undefined. I was trying to run close on the express request object. I had to export the server object from where express was instantiated:
afterAll(async () => {
try {
await server.close()
} catch (error) {
console.error(error)
throw error;
}
})
const app = express()
routes(app)
const server = app.listen(port, async err => {
...
module.exports = app
module.exports.server = server

Related

Unit Test with Express / Mocha / Mongodb Memory Server

I would like to configure my project in order to run unit test for some API endpoints (that call the database). I'm using :
ExpressJS
MongoDB (no Mongoose)
Mocha / Chai
Mongodb Memory Server (to mock the DB)
// app.ts
export const app = express();
const port = process.env.PORT;
app.use("/my-route", myRoutes);
mongoConnect().then(() => {
app.listen(port, () => {
console.log(`Listening on port ${port}`);
});
});
// database.ts
export const mongoConnect = async () => {
try {
let MONGODB_URI = process.env.MONGODB_URI;
if (process.env.NODE_ENV === "test") {
const mongoServer = await MongoMemoryServer.create();
MONGODB_URI = mongoServer.getUri();
}
const client: MongoClient = await MongoClient.connect(MONGODB_URI);
_db = client.db("dbName");
_mongoClient = client;
if (process.env.NODE_ENV === "test") {
console.log("Connected to MongoDB Test");
} else {
console.log("Connected to MongoDB");
}
} catch (err) {
console.log("Error connecting to MongoDB:", err);
throw err;
}
};
export const getMongoClient = () => {
if (_mongoClient) {
return _mongoClient;
}
throw "Mongo client doesn't exist";
};
export const getDb = () => {
if (_db) {
return _db;
}
throw "No database found!";
};
// test.ts
let mongoClient: MongoClient;
let db: Db;
before(function (done) {
mongoConnect()
.then(() => {
db = getDb();
mongoClient = getMongoClient();
return db.createCollection("wordsCollection");
})
.then(() => {
db.collection("wordsCollection").insertMany(data);
})
.catch((err) => console.log(err))
.finally(() => done());
});
after(function (done) {
db.dropDatabase();
mongoClient.close().then(() => {
done();
});
});
it("test", async function () {
let res = await chai
.request(app)
.post("/my-route/hello")
.send({ excludeIds: [] });
expect(res.status).to.equal(200);
});
});
But it's not working...
If I call mongoConnect() in test.ts it console.log twice Connected to MongoDB Test. But if I don't call the function it throws me error because MongoClient is undefined.
I think await chai.request(app) already calls the database and server but I need to create Collection and Documents before. So I need to connect to the DB before the test.
Any help would be greatly appreciated.
I found a solution, I don't know if it's best practice but it works and is pretty easy, thanks to this post : https://stackoverflow.com/a/70285190/10547153.
I needed to add a condition in app.ts before making the connection to the database and the server in order to launch them only if it's called by Node itself.
if (require.main === module) {
mongoConnect().then(() => {
app.listen(port, () => {
console.log(`Listening on port ${port}`);
});
});
}
When a file is run directly from Node.js, require.main is set to its
module. That means that it is possible to determine whether a file has
been run directly by testing require.main === module.
Now I can connect to the mocked database from test.ts and only one connection will be triggered.

Why does my node.js HTTP requests fail when my server is initialized by my test suite?

I have been trying to write a test suite to my node.js API project and one of their requirements is to control when the server starts and stops. For that, I wrote this code below with two functions: initializeWebServer and stopWebServer.
express.js
const initializeWebServer = () => {
return new Promise((resolve, reject) => {
app = express();
/* some middlewares */
app.use('/', userRoutes);
httpServer = http.createServer(app);
httpServer.listen(3000, (err) => {
if (err) {
reject(err);
return;
}
resolve(app);
});
});
};
const stopWebServer = () => {
return new Promise((resolve, reject) => {
httpServer.close(() => { resolve(); });
});
};
Using mocha to run my tests, I choose to manage my server connection with before and after hooks, using async/await syntax.
user.spec.js
let axiosAPIClient;
before(async () => {
await initializeWebServer();
const axiosConfig = {
baseURL: `http://localhost:3000`,
validateStatus: () => true
};
axiosAPIClient = axios.create(axiosConfig);
});
after(async () => {
await stopWebServer();
});
describe('POST /api/user', () => {
it('when add a new user, then should get back approval with 200 response', async () => {
const userData = {
/* user props */
};
const response = await axiosAPIClient.post('/api/user', userData);
expect(response).to.containSubset({
status: 200,
data: { message: 'User signed up.' }
});
When axios (I tried fetch too) submit any HTTP request, mocha returns the following error: Error: Timeout of 2000ms exceeded. For async tests and hooks, ensure "done()" is called; if returning a Promise, ensure it resolves.. I tried to increase the timeout interval, but it didn't work.
If I execute my test suite without hooks, initializing my server with nodemon pointing to my index file with the same await initializeWebServer();, HTTP requests work as it should.
I really don't understand why this is happening, I think it's something with mocha.
I did some debug tests and figured out that I forgot to set up a db connection when I run my test suite, including in before and after hooks. It was a simple mistake.
user.spec.js
let axiosAPIClient;
before(async () => {
await initializeWebServer();
db.setUp();
/* axios config etc. */
after(async () => {
await stopWebServer();
await db.closeConnection();
});

Open handles while testing Koa.js with Jest

Here's a trivial trivial koa server implementation:
const Koa = require('koa')
const api = require('./resources')
const createServer = () => {
const app = new Koa()
app.use(api.routes())
app.use(api.allowedMethods())
return app
}
module.exports = createServer
And a simple test:
const request = require('supertest')
const createServer = require('../src/server')
const { knex } = require('../src/config/db')
let server
beforeAll(() => {
server = createServer().listen(8081)
return server
})
beforeEach(() => {
return () => {
knex.migrate.latest()
knex.seed.run()
}
})
afterEach(() => {
return () => {
knex.migrate.rollback()
}
})
afterAll(() => {
server.close()
})
describe('routes: /api/users', () => {
describe('GET /users', () => {
test('should return json', async () => {
const response = await request(server).get('/api/users')
expect(response.status).toEqual(200)
expect(response.type).toEqual('application/json')
expect(response.body).toHaveLength(3)
})
})
})
I will skip the example route for brevity sake as the test passes successfully. But then it hangs, and running jest --detectOpenHandles produces:
Jest has detected the following 1 open handle potentially keeping Jest from exiting:
● TCPSERVERWRAP
6 |
7 | beforeAll(() => {
> 8 | server = createServer().listen(8081)
| ^
9 | return server
10 | })
11 |
at Application.listen (node_modules/koa/lib/application.js:82:19)
at Object.<anonymous> (__tests__/routes.test.js:8:27)
Why does this occur and how can I fix it?
There are several problems.
return server doesn't affect anything, it can be removed. But beforeAll doesn't wait for the server to start, it should be:
beforeAll(done => {
server = createServer().listen(8081, done)
})
beforeEach and afterEach return functions that are not executed, promises from Knex aren't chained, it should be:
beforeEach(async () => {
await knex.migrate.latest()
await knex.seed.run()
})
afterEach(async () => {
await knex.migrate.rollback()
})
afterAll doesn't wait for server connection to be closed, which is likely the cause for this error, it should be:
afterAll(done => {
server.close(done)
});

How to start an Express server in best practice?

I have an Express REST API server written in TypeScript.
At first, I started server like this -
const initServer = async() => {
await connectDb();
await server.listen(secrets.port, secrets.hostname, () => {
logger.info(
`Running server at http://${secrets.hostname}:${secrets.port} in ${
secrets.env
} env and API version is ${secrets.apiVersion}`
);
});
}
initServer().catch(error => logger.error(`Init server went wrong with: ${error}`));
Then I read a blog post suggesting to use .then().catch() -
async function initServer() {
// Connect the database first
await connectDb()
.then(() =>
// then start the server
server.listen(secrets.port, secrets.hostname, () => {
logger.info(
`Running server at http://${secrets.hostname}:${secrets.port} in ${
secrets.env
} env and API version is ${secrets.apiVersion}`
);
})
)
.catch(err => {
logger.error(`Initializing server went wrong with: ${err}`);
process.exit(1);
});
}
Then I read another blog post saying "catch the error first" -
async function initServer() {
// Connect the database first
await connectDb()
// then start the server
.then(() => server.listen(secrets.port, secrets.hostname))
.catch(err => {
logger.error(`Initializing server went wrong with: ${err}`);
process.exit(1);
})
// then announce the server info
.finally(() => {
logger.info(
`Running server at http://${secrets.hostname}:${secrets.port} in ${
secrets.env
} env and API version is ${secrets.apiVersion}`
);
});
}
But I feel like I'm not doing it right. Please educate me what I'm doing wrong.
How should I start the server?

node-mssql "connection is closed" when running Mocha test, but runs fine in app

I have a node.js data processing app that pulls some data from mssql. It runs fine and produces the expected results. However, the integration tests aren't working and I would like them to.
Below is the connection management and a test query function. I can see from output and running in the debugger that the test has run and failed before the database has connected. So it seems like my Mocha async setup isn't working, but it looks like everything I've seen in documentation.
node -v
v10.15.0
chai: "^4.2.0",
mocha: "^5.2.0"
mssql: "^4.3.0",
const config = require('./config')
const _ = require('underscore')
const sql = require('mssql')
sql.on('error', err => {
console.error('SQL Error', err)
})
let api = {}
api.connect = async dbConfig => {
return new sql.ConnectionPool(dbConfig).connect(
err => {
if (err)
console.error('Connection error', err)
else
console.log('connected')
})
}
var connecting = api.connect(config.sql)
api.simple = async () => {
let pool = await connecting
let result = await pool.request().query('select 1 as number')
return result.recordset[0].number
}
module.exports = api
Here is my mocha test for it that fails
const { expect } = require('chai')
const data = require('../src/data')
describe('data access', function () {
it('is simple', async function () {
const yo = await data.simple()
expect(yo).to.exist
expect(yo).to.equal(1)
})
})
I've also tried the older style of async mocha tests using done callbacks ala
it('is simple oldschool', function (done) {
data.simple()
.then(function(yo){
expect(yo).to.exist
expect(yo).to.equal(1)
done()
})
})
That times out no matter how long I set Mocha's timeout for (I tried as high as 60 seconds)
I'm at my wits end here, anyone see anything wrong?
api.connect can return before the connection is actually done. Rewriting it like this will make sure ConnectionPool.connect can finish before the api.connect promise resolves.
api.connect = dbConfig =>
new Promise((resolve, reject) => {
const pool = new sql.ConnectionPool(dbConfig);
pool.connect(err => {
if (err) {
console.error("Connection error", err);
return reject(err);
}
return resolve(pool);
});
});
Beyond that, I'm confused about let pool = await c3; there's no symbol c3 in the code you've pasted...
I think you are having a race condition with the database connection.
I do this in the before()
before((done) => {
server.on("serverStarted", function() {
done();
});
});
Then in my server (I'm using node), I emit when when I am connected:
var port = process.env.PORT || 3030;
var server = http.listen(port, function(){
console.log('listening on port: ' + port);
db.connect().then(() => {
console.log("Connect to database successful");
server.emit("serverStarted") // HERE IT IS
}).catch(err => {
console.error(err);
console.log("Cannot connect to database");
process.exit(1);
});
});
Hope this helps. I've pulled out some hair on this one.

Resources