How to test with Jest after connecting to MongoDB? - javascript

I'm trying to set up testing for various routes in my Express server that require connectivity to my MongoDB database.
I'm not sure how to structure the Jest file in order to allow for testing. In my normal index.js file, I'm importing the app, and running app.listen within the connect .then call, like this:
const connect = require("../dbs/mongodb/connect");
connect()
.then(_ => {
app.listen(process.env.PORT, _ => logger.info('this is running')
})
.catch(_ => logger.error('The app could not connect.');
I've tried running the same setup in my test.js files, but it's not working.
For example:
const connect = require("../dbs/mongodb/connect");
const request = require("supertest");
const runTests = () => {
describe("Test the home page", () => {
test("It should give a 200 response.", async () => {
let res = await request(app).get("/");
expect(res.statusCode).toBe(200);
});
});
};
connect()
.then(_ => app.listen(process.env.PORT))
.then(runTests)
.catch(err => {
console.error(`Could not connect to mongodb`, err);
});
How is it possible to wait for a connection to MongoDB before running my tests?

So, turns out there were a few changes that I had to make. Firstly, I had to load in my .env file before running the tests. I did this by creating a jest.config.js file in the root of my project:
module.exports = {
verbose: true,
setupFiles: ["dotenv/config"]
};
Then within the actual testing suite, I'm running beforeEach to connect to the MongoDB server.
const connect = require("../dbs/mongodb/connect");
const app = require("../app");
const request = require("supertest");
beforeEach(async() => {
await connect();
});
describe("This is the test", () => {
test("This should work", async done => {
let res = await request(app).get("/home");
expect(res.statusCode).toBe(200);
done();
})
});

Related

How can I stop getting the 'Can't resolve async_hooks' error when using npm start?

I've been following a tutorial on how to connect to a mongoDB collection. The tutorial works fine but I'm trying the same code in a project I'm working on and have been getting the following error constantly:
./node_modules/raw-body/index.js
Module not found: Can't resolve 'async_hooks' in '*:\*\*\Desktop\Projects\testing-area\node_modules\raw-body'
I've tried:
-deleting node_modules and then running npm install
-running npm update to bring all dependencies to the latest version
-updating npm itself
I've read that async_hooks is used for backend work and if you try to use it in the frontend, it can cause this issue. Problem is, I don't really know a way around it.
Here's the code I'm trying to use to connect to the mongodb collection:
//give functions of mongo db to MongoClient
const { MongoClient } = require('mongodb')
let dbConnection
const bark = (input) => {
console.log(input)
}
module.exports = {
connectToDb: (cb) => {
MongoClient.connect("mongodb://localhost:27017/Treasures")
.then((client) => {
dbConnection = client.db()
return cb()
})
.catch(err => {
bark("----------")
bark("Pants shat when trying to connect to DB:\n")
bark(err)
return cb(err)
bark("----------")
})
},
getDb: () => dbConnection
}
And then in a component I have this, to try and get the data from the collection:
// Imports/Requires for mongoDb connection
const { ObjectID } = require("bson");
const express = require("express");
const { connectToDb, getDb } = require("../../db")
// COMPONENT STARTS HERE:
export const TreasureGen = () => {
//init app and middleware
const app = express();
//db connection
let db
connectToDb((err) => {
if(!err)
{
app.listen(3000, () => {
bark("App listening on port 3000")
})
db = getDb()
}
})

Next.js grpc-node usage

I use gRPC but I have a problem initializing the service in Next.js app.
Goal: Create client service only once in app and use it in getServerSideProps (app doesn't use client-side routing).
For example, we have a service generated with grpc-tools (only available on SSR) and then I just want to initialize it somewhere. At first I thought it can be realized in a custom server.js:
const { credentials } = require('#grpc/grpc-js');
const express = require("express");
const next = require("next");
const { MyserviceClient } = require('./gen/myservice_grpc_pb');
const dev = process.env.NODE_ENV !== "production";
const app = next({ dev });
const handle = app.getRequestHandler();
// Init & Export
exports.myService = new MyserviceClient(
'http://localhost:3000',
credentials.createInsecure(),
);
(async () => {
await app.prepare();
const server = express();
server.get("*", (req, res) => handle(req, res));
server.listen(process.env.PORT, () => {
console.log(`Listening at http://localhost:${process.env.PORT}`);
});
})();
And then use it on the homepage, for example:
import React from 'react';
const { GetSmthRequest } = require('../gen/myservice_pb');
const { myService } = require('../server.js');
const IndexPage = () => (
<div>
<span>My HomePage</span>
</div>
)
const getServerSideProps = async () => {
const request = new GetSmthRequest();
request.setSomeStuff('random');
myService.getStmh(GetSmthRequest, (err, res) => {
//...
})
return {
props: {
}
}
}
export default IndexPage;
But for some reason it's not possible to initialize the client service in the server.js.
Also I tried doing it with next.config.js:
const { credentials } = require('#grpc/grpc-js');
const { MyserviceClient } = require('./gen/myservice_grpc_pb');
module.exports = {
serverRuntimeConfig: {
myService: new MyserviceClient(
'http://localhost:3000',
credentials.createInsecure(),
),
},
};
This solution works, so I can use the service through serverRuntimeConfig, thereby initializing it only once in the entire application, but when I make a request somewhere using getServerSideProps, I get an error:
Request message serialization failure: Expected argument of type ...
Error explanation: (https://stackoverflow.com/a/50845069/9464680)
That error message indicates that message serialization
(transformation of the message object passed to gRPC into binary data)
failed. This generally happens because the message object doesn't
match the expected message type or is otherwise invalid
Does anyone know why I am getting this error?
It's also interesting to see some examples of using Next.js with grpc-node.
For such a case you can use Node.js global

jest asynchronous test ignoring expect() function

server.js is a simple express.js file that uses jwt tokens. I currently want to test a simple route that will only return the string "Hello World" as shown below
app.get("/", (req, res) => {
res.send("Hello World");
});
The code below is my jest file that is using supertest to send the request along with the valid jwt token
const supertest = require("supertest");
let server, request;
const serverStart = new Promise(resolve => {
server = require("../server.js");
request = supertest(server);
server.on("app_started", () => {
resolve();
});
});
beforeAll(async () => {
await serverStart;
});
afterAll(() => {
server.close();
});
describe("When testing the server.js", () => {
it("Should connect successfully and be able to return a response", async () => {
const response = await request
.get("/")
.set("Authorization", `bearer ${process.env.AUTHTOKEN}`);
expect(response.text).toBe("Hello World");
console.log(response.text);
});
});
When running this jest (after it's timeout of 5 seconds) says Timeout - Async callback was not invoked within the 5000ms timeout specified however, the console.log that I have added after the expect function outputs "Hello World" to the console meaning the request is made and returns a value but it carriers on with the code but is just skipping the expect function
I've also tried this with done() and also using a then() but got the same error both times and I've console logged the time before and after the call and found it only takes a few milliseconds to return a value, so why does the expect not seem to complete the test?
Pretty sure your problem is the app_started event that you are listening to. I don't know where that event is documented. I think you should use listening instead. I'm going to make some assumptions about your server.js file.
The following test passes. I think your tests never actually start because you are listening for an event that will never be fired.
This is the server.js file that I am testing with:
const http = require("http");
const express = require("express");
const app = express();
app.get("/", (req, res) => {
res.send("hello");
});
const server = http.createServer(app);
server.listen(8081);
module.exports = server;
This is my test file server.test.js:
const supertest = require("supertest");
let server, request;
const serverStart = new Promise(resolve => {
server = require("./server.js");
request = supertest(server);
server.on("listening", () => resolve());
});
beforeAll(async () => {
await serverStart;
});
afterAll(() => {
server.close();
});
describe("server", () => {
it("should get hello", async () => {
const response = await request.get("/");
expect(response.text).toBe("hello");
});
});
User zero298 pointed out that the test was failing during the beforeAll() function because server.on() wasn't returning anything. In the end I wrote the promise inside the server.js which resolves after it has started and then exported this promise.
let server;
const serverStart = new Promise(resolve => {
server = app.listen(port, err => {
if (err) {
console.log(err);
}
console.log("Listening on port " + port);
resolve();
});
});
module.exports = app;
module.exports.close = () => server.close();
module.exports.serverStart = serverStart;
and the beforeAll() in server.test.js now looks like
const server = require("../server.js");
let request;
beforeAll(async () => {
await server.serverStart;
request = supertest(server);
});

How to write integration test with Jest and Express?

I am trying to write integration tests with a third party microservice (Assume it is call Service). When a request is sent to Service, it will fire a webhook. What I am trying to do is to test it the webhook parsed correctly.
import express from "express";
import bodyParser from "body-parser";
import { handleHook} from "./hook";
export const app = express();
app.use(bodyParser.json(), handleHook);
describe("integration", () => {
test("hook", async () => {
const mock = jest.fn();
app.post("/", (req, res) => {
mock(req);
res.send("");
});
const p = new Promise<Server>(resolve => {
const server = app.listen(3000, () => {
resolve(server);
});
});
const server = await p;
await upload();
jest.useFakeTimers();
setTimeout(() => {
server.close();
}, 15000);
jest.runAllTimers();
expect(mock).toHaveBeenCalledTimes(4);
});
});
However, I got two problems. First, I got
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
but using --detectOpenHandles does not show how to troubleshoot this issue.
Second, because the webhook is fire asynchronously, how can I ensure to catch all of them or wait for a period of time to timeout.

Node.js Async/Await module export

I'm kinda new to module creation and was wondering about module.exports and waiting for async functions (like a mongo connect function for example) to complete and exporting the result. The variables get properly defined using async/await in the module, but when trying to log them by requiring the module, they show up as undefined. If someone could point me in the right direction, that'd be great. Here's the code I've got so far:
// module.js
const MongoClient = require('mongodb').MongoClient
const mongo_host = '127.0.0.1'
const mongo_db = 'test'
const mongo_port = '27017';
(async module => {
var client, db
var url = `mongodb://${mongo_host}:${mongo_port}/${mongo_db}`
try {
// Use connect method to connect to the Server
client = await MongoClient.connect(url, {
useNewUrlParser: true
})
db = client.db(mongo_db)
} catch (err) {
console.error(err)
} finally {
// Exporting mongo just to test things
console.log(client) // Just to test things I tried logging the client here and it works. It doesn't show 'undefined' like test.js does when trying to console.log it from there
module.exports = {
client,
db
}
}
})(module)
And here's the js that requires the module
// test.js
const {client} = require('./module')
console.log(client) // Logs 'undefined'
I'm fairly familiar with js and am still actively learning and looking into things like async/await and like features, but yeah... I can't really figure that one out
You have to export synchronously, so its impossible to export client and db directly. However you could export a Promise that resolves to client and db:
module.exports = (async function() {
const client = await MongoClient.connect(url, {
useNewUrlParser: true
});
const db = client.db(mongo_db);
return { client, db };
})();
So then you can import it as:
const {client, db} = await require("yourmodule");
(that has to be in an async function itself)
PS: console.error(err) is not a proper error handler, if you cant handle the error just crash
the solution provided above by #Jonas Wilms is working but requires to call requires in an async function each time we want to reuse the connection. an alternative way is to use a callback function to return the mongoDB client object.
mongo.js:
const MongoClient = require('mongodb').MongoClient;
const uri = "mongodb+srv://<user>:<pwd>#<host and port>?retryWrites=true";
const mongoClient = async function(cb) {
const client = await MongoClient.connect(uri, {
useNewUrlParser: true
});
cb(client);
};
module.exports = {mongoClient}
then we can use mongoClient method in a diffrent file(express route or any other js file).
app.js:
var client;
const mongo = require('path to mongo.js');
mongo.mongoClient((connection) => {
client = connection;
});
//declare express app and listen....
//simple post reuest to store a student..
app.post('/', async (req, res, next) => {
const newStudent = {
name: req.body.name,
description: req.body.description,
studentId: req.body.studetId,
image: req.body.image
};
try
{
await client.db('university').collection('students').insertOne({newStudent});
}
catch(err)
{
console.log(err);
return res.status(500).json({ error: err});
}
return res.status(201).json({ message: 'Student added'});
};

Categories