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.
Related
I'm trying to create a basic caching app just to test redis. Im using Redis Version: 4.0.6.
First I was getting error clientclosederror: the client is closed.
Then, after reading the docs, I added
let client;
(async ()=> {
client = redis.createClient()
await client.connect()
})();
But now, when trying on Postman, it just hangs, no response is returned
Full Code:
const express = require("express");
const redis = require("redis");
const axios = require('axios')
const app = express();
let client;
(async ()=> {
client = redis.createClient()
await client.connect()
})();
app.get('/result', async (req, res) => {
const searchTerm = req.query.name;
try {
await client.get(searchTerm, async (err, result) => {
console.log('cached called')
if (err) throw err;
if (result) {
res.status(200).send({
result: JSON.parse(result),
message: "data retrieved from the cache"
});
}
else {
const result = await axios.get(`https://api.agify.io/?name=${searchTerm}`);
await client.set(searchTerm, JSON.stringify(result.data));
return res.status(200).send({
result: result.data,
message: "cache miss"
});
}
})
} catch (error) {
console.log('get error', error)
return res.status(500).send({ message: error.message })
}
})
app.listen(process.env.PORT || 3000, () => {
console.log("Node server started");
});
client.get doesn't need a callback function. It's async. My guess is that it's never getting called and thus Express is not returning anything.
Try this instead:
const result = await client.get('foo')
if (result !== null) {
// it's a hit
} else {
// it's a miss
}
I tried to connect oracle database to my project. I used the createpool in order to call this function in the future for all the necessary requests from the database. my config.js file:
const oracledb = require('oracledb')
oracledb.outFormat = oracledb.OUT_FORMAT_OBJECT
const init = async function (query) {
try {
await oracledb.createPool({
user: 'almat',
password: 'almat789456123',
connectString: '(DESCRIPTION=(ADDRESS=(PROTOCOL=TCP)(HOST=localhost)(PORT=1521))(CONNECT_DATA=(SERVER=DEDICATED)(SERVICE_NAME=xepdb1)))'
})
console.log('Connection pool started')
await dostuff(query)
} catch (err) {
console.error('init() error: ' + err.message)
} finally {
// await closePoolAndExit()
}
}
async function dostuff (query) {
let connection
try {
connection = await oracledb.getConnection()
const sql = query
const binds = [1]
const options = { outFormat: oracledb.OUT_FORMAT_OBJECT }
const result = await connection.execute(sql, binds, options)
console.log(result)
} catch (err) {
console.error(err)
} finally {
if (connection) {
try {
await connection.close()
} catch (err) {
console.error(err)
}
}
}
}
async function closePoolAndExit () {
console.log('\nTerminating')
try {
await oracledb.getPool().close(10)
console.log('Pool closed')
process.exit(0)
} catch (err) {
console.error(err.message)
process.exit(1)
}
}
process
.once('SIGTERM', closePoolAndExit)
.once('SIGINT', closePoolAndExit)
module.exports.init = init
My app.js file:
const express = require('express');
const config = require('./utils/config');
const app = express();
app.listen(3000, function () {
console.log('Server running at port 3000')
})
app.set('view engine', 'ejs')
app.get('/', function (req, res) {
return res.render('index')
})
app.get('/login', function (req, res) {
return res.render('login')
})
app.get('/getCustomerName', function (req, res) {
const query = 'SELECT firstname FROM customer WHERE :b = 1'
const result = config.init(query)
//console.log(typeof result)
return res.send(result)
})
module.exports = app
When I request http://localhost:3000/getCustomerName it returns empty json file and terminal throws this error: NJS-047: poolAlias "default" not found in the connection pool cache
The createPool() call should be run once during app initialization, eg around the time you call express(). From the createPool() doc:
This method creates a pool of connections with the specified user name, password and connection string. A pool is typically created once during application initialization.
init() shouldn't call doStuff(). Once the pool is created, then your web listener handlers can call dostuff().
Look at the basic example webapp.js.
Also see the Oracle Magazine series Build REST APIs for Node.js which has source code here.
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.
The problem that I have is that my express server starts before the database connection is established. People can send requests to the application while the connection is not yet there for some time:
const app = express();
dbClient.connect()
.subscribe(() => console.log('connection established!'));
module.exports = app.listen(8080, () => {
console.log('the server is running');
});
The outcome of this is:
the server is running // some seconds break
connection established! // now everything works properly
How can I start listening to events only after the subscriber has been run?
The only way to ensure you are connected before to listen is to chain it. If you experience trouble about exporting the result is because you are mistaking import/export. Import/export should not have any impact on your system. Every actions should be triggered and not implied.
You should consider putting all your Express handling into a class and then use it in your controller. This way you could handle errors ... As example :
// File a.js
let instance = null;
export default class ServerApi {
constructor() {
if (instance) return instance;
instance = this;
return instance;
}
static getInstance() {
return instance || new ServerApi();
}
startServer(callback) {
const app = express();
dbClient.connect()
.subscribe(() => {
console.log('connection established!');
app.listen(8080, () => {
console.log('the server is running');
callback();
});
});
}
stopServer() { ... }
getServerStatus() { ... }
};
// File controller.js
import ServerApi from 'a.js';
ServerApi.getInstance().startServer(() => {
// handle error
// handle success
});
There are few ways to achieve this.
one a ways to wrap app.listen into observable
const app = express();
const connectApp = (port = 8080) => new Observable(observer => app.listen(port, () => {
observer.next(app);
observer.complete();
});
dbClient.connect()
.pipe(
tap(() => console.log('connection established!')),
mergeMap(() => connectApp()),
tap(() => console.log('the server is running')),
)
.subscribe(() => console.log('Enjoy'));
I cannot make MongoClient.connect to await for the results before going on.
I am trying to pass db from the server.js, where I connect my mongoclient, to my routes/api.js where I do my post requests. But it does not work, I always get:
TypeError: Cannot read property 'collection' of undefined
Here is my routes/api.js:
var db = require("../server");
router.post('/video_url', async (req, res) => {
const cursor = db.collection('movie').findOne({ link: req.body.videoURL }, function (findErr, result) {
if (findErr) throw findErr;
console.log(cursor)
});
server.js:
var db = async function () {
return await MongoClient.connect(MONGODB_URI, function(err, client) {
try {
if(err) throw err;
db = client.db('sub-project');
// Start the application after the database connection is ready
app.listen(PORT, () => console.log(`Server listening on port ${PORT}`));
return db;
}
catch(ex) {
console.log(ex)
}
});
}
module.exports = db;
EDIT:
var dbObject = (async function() {
var connection = await new Promise(function(resolve, reject) {
MongoClient.connect(MONGODB_URI, { useNewUrlParser: true }, function(err, client) {
try {
if (err) throw err;
db = client.db('sub-project');
// Start the application after the database connection is ready
app.listen(PORT, () => console.log(`Server listening on port ${PORT}`));
resolve(db);
} catch (ex) {
console.log(ex)
reject(ex);
}
});
});
return connection;
})();
console.log("TYPEOF DB IN", typeof(dbObject))
console.log("TYPEOF DB.COLLECTION IN", typeof(dbObject.collection))
The both console.log() are undefined... is that normal?
Use this code for your server.js. Your code was not working because your function was not getting called when you are requiring it.
var dbObject;
(function() {
MongoClient.connect(MONGODB_URI, { useNewUrlParser: true }, function(err, client) {
try {
if (err) throw err;
db = client.db('sub-project');
// Start the application after the database connection is ready
app.listen(PORT, () => console.log(`Server listening on port ${PORT}`));
dbObject = db;
} catch (ex) {
console.log(ex);
}
});
})();
setTimeout(function() {
console.log("TYPEOF DB IN", typeof(dbObject))
console.log("TYPEOF DB.COLLECTION IN", typeof(dbObject.collection))
}, 2000);
module.exports = dbObject;