I have a simple postgres.js wrapper file.
const pg = require("pg")
const pool = new pg.Pool({ connectionString: process.env.DATABASE_URL });
function close() {
return pool.end()
}
module.exports = {
end: pool.end,
close
};
Running a jest testcase that utilizes the postgres library above, like so:
const postgres = require("./path/to/postgres.js");
describe("Foo", () => {
afterAll(() => {
return postgres.end();
})
it(...)
});
Will yield the "This usually means that there are asynchronous operations that weren't stopped in your tests." error message and hangs there.
However, if I change the line postgres.end() to postgres.close(), it correctly closes the DB connection and jest terminates after the test is done.
My question is that doesn't close and end functionally does the same thing? Why does one close the connection but the other doesn't?
Your end function is just executing the pool.end and not returning it as a promise, so it's not similar.
For a better visualization, your current code is basically doing this:
function close() {
return pool.end()
}
function end() {
pool.end()
}
module.exports = {
end,
close
};
Related
I am unit testing (using jest) a function named "getTripDetails" (inside file trip.js) that calls another file "getTrace.js" from different module (which exports a function as shown below).
I want to mock the call of function "getTrace" while testing "getTripDetails" function.
file: trips.js
const gpsTrace = require("./gpsTrace");
getTripDetails = async(req, res)=>{
let gpsTraceRes = await gpsTrace(req.body, req.adToken)
//more code...
return {status:200};
}
file: getTrace.js
module.exports = async(payload, token) =>{
try {
//code
} catch (e) {
error(e)
throw new Error(e)
}
}
This is what i tried after reading the docs.
file: test.js
let ctrl = require("./trips");
describe("API -- testing", function () {
it("Trip details", async function () {
jest.mock('./gpsTrace');
const gpsTrace = require('./gpsTrace');
gpsTrace.mockImplementation(() => {});
gpsTrace();
await ctrl.getTripDetails({},{});
expect(response.status).to.eql(200);
});
});
It did not get mocked, instead it was calling the original implementation.
Any suggesstions?
You were pretty close! Here are the updated files with comments describing the changes:
gpsTrace.js
Added a console.log message. We won't see this in the test if the mock works successfully.
module.exports = async (payload, token) => {
try {
//code
console.log("You won't see me in the Jest test because of the mock implementation")
} catch (e) {
error(e)
throw new Error(e)
}
}
trips.js
You needed to export your code to be used in other modules. Seeing as you're calling ctrl.getTripDetails() in the test, it makes sense to export your getTripDetails() on an object at the bottom of the file.
const gpsTrace = require("./gpsTrace");
const getTripDetails = async (req, res) =>{
let gpsTraceRes = await gpsTrace(req.body, req.adToken)
//more code...
return { status:200 };
}
module.exports = {
getTripDetails,
}
gpsTrace.test.js
Make sure to import your modules at the top of the file. Remember that ctrl.getTripDetails({}, {}) calls gpsTrace internally, so no need to call it twice in your test. You also needed to save the response returned from getTripDetails into a variable to be able to compare it: const response = await ctrl.getTripDetails({}, {});.
// make sure your require statements go at the top of the module
const gpsTrace = require('./gpsTrace');
let ctrl = require("./trips");
jest.mock('./gpsTrace');
gpsTrace.mockImplementation(() => {});
describe("API -- testing", function () {
it("Trip details", async function () {
// ctrl.getTripDeals() calls your gpsTrace function internally, so no need to call it twice
// gpsTrace(); <-- can be removed
// you needed to save the returned response into a variable to be able to test it.
const response = await ctrl.getTripDetails({}, {});
expect(response.status).toEqual(200);
});
});
Result
After running the test it now successfully passes. Notice that we DO NOT see the console.log message in the gpsTrace function, which indicates our mockedImplementation of the function is working in the test script. ๐
I want my nodejs app to not continue unless it connects to mongodb.
I tried:
//Mongo
async function mongoConnect(mongoDB) {
var mongoConnectionSuccessful = false;
LOG("Connecting to mongodb at " + mongoDB + "...");
while (!mongoConnectionSuccessful) {
try {
await mongoose.connect(mongoDB, { useNewUrlParser: true, useUnifiedTopology: true });
LOG("connected!");
mongoConnectionSuccessful = true;
mongoose.connection.on('error', ()=>LOG('MongoDB connection error:'));
return;
} catch (error) {
LOG(error);
}
await utils.sleep(500);
}
}
mongoConnect(config.mongoUrl);
but in order to use await in mongoose.connect, I must make mongConnect async, but I then cannot call it in a blocking way from the code because in order to call like this, I must call with await, but await is only permitted inside async functions.
I must call with await, but await is only permitted inside async functions
That's correct. So do just that:
async function main () {
await mongoConnect(config.mongoUrl);
// rest of your code...
}
main();
For example if this is an Express server do something like:
const express = require('express');
const app = express();
async function main () {
await mongoConnect(config.montoUrl);
const routes = require('./my-routes');
app.use(routes);
app.listen(config.port);
}
main();
You want to attempt reconnect if mongoose fails to connect. Here is an example logic without helper lib.Props to the guy who posted this solution in a github issue for mongoose. Here
function createConnection (dbURL, options) {
var db = mongoose.createConnection(dbURL, options);
db.on('error', function (err) {
// If first connect fails because mongod is down, try again later.
// This is only needed for first connect, not for runtime reconnects.
// See: https://github.com/Automattic/mongoose/issues/5169
if (err.message && err.message.match(/failed to connect to server .* on first connect/)) {
console.log(new Date(), String(err));
// Wait for a bit, then try to connect again
setTimeout(function () {
console.log("Retrying first connect...");
db.openUri(dbURL).catch(() => {});
// Why the empty catch?
// Well, errors thrown by db.open() will also be passed to .on('error'),
// so we can handle them there, no need to log anything in the catch here.
// But we still need this empty catch to avoid unhandled rejections.
}, 20 * 1000);
} else {
// Some other error occurred. Log it.
console.error(new Date(), String(err));
}
});
db.once('open', function () {
console.log("Connection to db established.");
});
return db;
}
// Use it like
var db = createConnection('mongodb://...', options);
and with a lib promise-retry
const promiseRetry = require('promise-retry')
const options = {
useNewUrlParser: true,
reconnectTries: 60,
reconnectInterval: 1000,
poolSize: 10,
bufferMaxEntries: 0 // If not connected, return errors immediately rather than waiting for reconnect
}
const promiseRetryOptions = {
retries: options.reconnectTries,
factor: 2,
minTimeout: options.reconnectInterval,
maxTimeout: 5000
}
const connect = () => {
return promiseRetry((retry, number) => {
logger.info(`MongoClient connecting to ${url} - retry number: ${number}`)
return MongoClient.connect(url, options).catch(retry)
}, promiseRetryOptions)
}
module.exports = { connect }
I found the solution in this article
I beleive what you need is to promise your call of mongoConnect(config.mongoUrl),
than await promises until it is called back.
async function getConcurrently() {
let promises = [];
promises.push(mongoConnect(config.mongoUrl))
// promises.push(getUsers());
// promises.push(getCategories());
// promises.push(getProducts());
let mongo = await Promise.all(promises);
//let [users, categories, products] = await Promise.all(promises);
}
Please Note this warning in the article:
As the first example, first we create an array of Promises (each one of the get functions are a Promise). Then, we execute all of them concurrently and simultaneously, awaiting for all of them to finish (await Promise.all). Finally, we assign the results to the respective variables users, categories and products. Despite the fact that it works, itโs important to say that using Promises.all() for everything is a bad idea.
I assume that for some reason you have incoming events meanwhile you are attempting to connect to mongo. My approach would be preventing any incoming events (such as starting the server and setting listeners) before connection to mongo. If it's not possible, one way to do it is to use a self-invoking function.
//Mongo
async function mongoConnect(mongoDB) {
var mongoConnectionSuccessful = false;
LOG("Connecting to mongodb at " + mongoDB + "...");
while (!mongoConnectionSuccessful) {
try {
await mongoose.connect(mongoDB, { useNewUrlParser: true, useUnifiedTopology: true });
LOG("connected!");
mongoConnectionSuccessful = true;
mongoose.connection.on('error', ()=>LOG('MongoDB connection error:'));
return;
} catch (error) {
LOG(error);
}
await utils.sleep(500);
}
}
(async function() {
// do stuff
await mongoConnect(config.mongoUrl);
// do other stuff
})();
I am trying to make a connection to a database and would like to see that my code stops executing while the connection has not been established yet. So it can print an error/success message to the console at the right time (before the program will output that startup was successful).
My current code to establish the connection is:
dbConnectAsync = async () => {
try {
await mongoose.connect("mongodb://localhost:27017/meetstation", { useNewUrlParser: true });
console.log(SUCCESS_MSG);
} catch (err) {
console.log(ERROR_MSG);
console.log(err.name);
}
}
I know it is possible to put all other code inside the try block but this is not desirable as I plan on moving the function that establishes the database connection to an other file.
Is there any way to call this function that forces other execution of code to wait until this function is done executing?
Within your code you can await dbConnectAsync and then run after the successful connection occurs. So code will appear in a separate try/catch block, but not in the internal try/catch of dbConnectAsync.
async function program() {
try {
await dbConnectAsync();
// code that executes after successful connection
} catch (err) {
// handle connection error
}
}
One change I would mention is to use throw in the catch block of dbConnectAsync so that any consuming code can respond to it. My example above won't receive any errors as a result.
Actually your server can't do much without a database. Therefore the most appropriate reaction in case of an error is just to crash. With top-level await I would just write a module like this:
export * from "mongoose";
import { connect } from "mongoose";
await connect("mongodb://localhost:27017/meetstation", { useNewUrlParser: true });
Then whenever you use mongoose, import it from that file and not from "mongoose" itself. That way, no code will run until the database is ready and in case of an error the server does crash.
You could extract the database setup function into module:
// db.js
let connection;
export async function setup() {
try {
connection = await mongoose.connect('mongodb://localhost:27017/meetstation', { useNewUrlParser: true });
console.log(SUCCESS_MSG);
} catch (err) {
console.log(ERROR_MSG);
console.log(err.name);
}
}
export function getConnection() {
return connection;
}
Init the connection and then start your application:
// main.js
import { setup, getConnection } from './db.js';
(async function bootstrap() {
await setup();
// start your application
// using getConnection()
})();
I got two problems with this jest test:
Is it possible to define the Content collection only once instead of doing that inside of the test?
I do get this error:
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.
I don't see why my async code weren't stopped...
import resolvers from 'resolvers/'
import Db from 'lib/db'
const db = new Db()
describe('Resolver', () => {
let token
beforeAll(async () => {
await db.connect()
})
beforeEach(async () => {
token = 'string'
await db.dropDB()
})
afterAll(async () => {
await db.connection.close()
})
describe('articleGetContent()', () => {
test('should return dataset', async () => {
// SETUP
const Content = db.connection.collection('content')
const docs = [{
// some content...
}]
await Content.insertMany(docs)
// EXECUTE
const result = await resolvers.Query.articleGetContent({}, {
id: '123,
language: 'en'
}, {
token
})
// VERIFY
expect.assertions(1)
expect(result).toBeDefined()
})
})
})
resolver
import { articleGetContent } from '../models/article'
export default {
Query: {
articleGetContent: async (obj, { id }, { token }) => articleGetContent(id, token)
}
}
This is how my db class looks like
db.js
export default class Db {
constructor (uri, callback) {
const mongo = process.env.MONGO || 'mongodb://localhost:27017'
this.mongodb = process.env.MONGO_DB || 'testing'
this.gfs = null
this.connection = MongoClient.connect(mongo, { useNewUrlParser: true })
this.connected = false
return this
}
async connect (msg) {
if (!this.connected) {
try {
this.connection = await this.connection
this.connection = this.connection.db(this.mongodb)
this.gfs = new mongo.GridFSBucket(this.connection)
this.connected = true
} catch (err) {
console.error('mongo connection error', err)
}
}
return this
}
async disconnect () {
if (this.connected) {
try {
this.connection = await this.connection.close()
this.connected = false
} catch (err) {
console.error('mongo disconnection error', err)
}
}
}
async dropDB () {
const Content = this.connection.collection('content')
await Content.deleteMany({})
}
}
Related to the second question I hope you've found some issues on github about it.
In general, the issue is described in the debug log.
Jest works with promises, as a result, you shouldn't leave any async operations in any status except resolved.
In your case, you have your DB connection opened so you need to implement another method disconnect for your DB class, this link to docs will help you, but I guess you have it already as it's not the full db.js file ( I see some custom method dropDB. Main idea here is to have it in afterAll hook:
afterAll(() => db.disconnect());
Great example at the bottom of the page
What about the first question, it really depends on what you are doing in your method dropDB. If you're running method for dropping collection, you could store the reference to this collection somewhere outside and use it as it will automatically create the new one, but it would be great to see this method.
Additionally, your async test was created in a wrong way, you could read more here for example in my Update. You need to run this function in the beginning of the test: expect.assertions(number)
expect.assertions(number) verifies that a certain number of assertions
are called during a test. This is often useful when testing
asynchronous code, in order to make sure that assertions in a callback
actually got called.
I'm trying to build some test helper functions that will help me to run some hooks with Mocha to test my GraphQL queries(just that you understand the context).
I want to do the following steps each time before I run the tests:
Connect to mongoDB with mongoose
Start the server (node)
Add some test data directly into the database (via mongoose models)
// this function returns back the server instance because I need it in the 'after' hook to be able to stop it after the tests execution.
export const startServer = () => {
mongoose.Promise = global.Promise;
mongoose.connect(MONGO_URI_TEST);
return mongoose.connection.once('open', () => {
const app = express();
app.post('/graphql', bodyParser.json(), graphqlExpress(req => {
return { schema: executableSchema };
})
);
return app.listen(9000, () => {
console.log('Server started!');
});
});
};
// now here's the before hook where the server is started
let server;
before( done => {
server = startServer(done); // here I need to wait
// some other async code that inserts test data into mongo
const user = new User({email: test#test.com});
user.save().then(() => {
// saved successfully
done();
})
});
I'm quite new in the JS world, how do you manage to wait (without async await syntax) until all the promises from startServer function are resolved and you get back the server instance and just after start inserting data into the database? It's a dummy question but also some links with a better explanation of this concept that I have here would be appreciated.
LE: The entire question has reduced to the followings:
const createServer = () => {
const server = app.listen(port, () => {
//callback body here
});
};
How to convert the callback to a Promise and at the same time return a valid server reference such that in the end I can do this:
const someOtherFunction = () => {
createServer().then(myValidServerInstance => {
//do something with that instance
}
}
TLDR: Some of the things about JS Promises were not clear for me, like for example the returning of the result via resolve() method.
So the right answer looks like this:
export const startServer = () => {
mongoose.Promise = global.Promise;
// first mistake was the use of callbacks rather than Promise approach offered by mongoose.connect
return mongoose.connect(MONGO_URI_TEST).then(() => {
console.log('Connected to MongoLab TEST instance.');
return createServer();
},
err => {
console.log('Error connecting to MongoLab TEST instance:', err);
});
};
The second one was returning directly the result of listen() before the operation get finished. So I've moved the code that is starting the server in another method and wrap the result of listen() into a promise and resolve the promise only when the server actually started listening.
const createServer = () => {
const app = express();
app.post('/graphql', bodyParser.json(), graphqlExpress(req => {
return {
schema: executableSchema,
context: { headers: req.headers},
};
})
);
return new Promise((resolve, reject) => {
const server = app.listen(9000, () => {
if (server) {
console.log('Server started on port 9000');
resolve(server);
} else {
reject();
}
});
});
};