I am trying learn to use jest and supertest to run my api test. It is my first time and I am following a tutorial. However, my test has refused to work. I am getting these errors:
thrown: "Exceeded timeout of 80000 ms for a test.
Use jest.setTimeout(newTimeout) to increase the timeout value, if this is a long-running test."
And this error at the same time:
st 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 have tried npm test --detectOpenHandles to see the asyn operations that werent stopped but I didnt see any.
I am making a test of my API routes. Here is my test:
const request = require('supertest')
describe('Test POST /register', ()=>{
test('It should respond with 200 success', async ()=>{
const response = await request(app).post('/register');
expect(response.statusCode).toBe(200);
},80000);
test('It should catch password must be present', ()=>{});
The route that I am testing:
router.post('/register', register);
The app.js file:
const authRoute = require("./routes/auths")
My server.js file:
const server = http.createServer(app);
const start = async () => {
try {
await connectDB(process.env.MONGO_URL)
server.listen(port, () =>
console.log(`Server is listening on port ${port}...`)
);
} catch (error) {
}
};
start();
It is a MERN stack with redis database instance. The redis is used to store users' refresh tokens
How can I fix this?
Related
I have a MERN stack application that I am running a test on using jest and supertest. I am new to testing and was following a tutorial. However, mine keep failing. My MERN stack application has redis database instance for storing users' refresh token. It also uses node crone to carry out scheduled background tasks.
I am getting these errors when I ran my tests:
thrown: "Exceeded timeout of 5000 ms for a test.
Use jest.setTimeout(newTimeout) to increase the timeout value, if this is a long-running test."
And 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 have used npm test --detectOpenHandles to see if I could detect the asynchronous operations but I didnt see any. Like, it wasn't revealed.
Here is my test:
const request = require('supertest')
const app = require('../app')
describe('Test POST /register', ()=>{
test('It should respond with 200 success', async ()=>{
const response = await request(app).post('/register');
expect(response.statusCode).toBe(200);
},);
test('It should catch password must be present', ()=>{});
})
I have tried adding this line of code to the test:
afterAll(async () => { await app.close(); });
It didn't resolve it. I have also tried adding done(), it didn't work too. I have also increased the timeOut which I added as second argument in test, still the same thing.
I am testing my routes and here is the current one I tried which is register route.
router.post('/register', register);
Here is the app.js file
app.use("/api/v1/auth", authRoute);
app.use("/api/v1/users",userRoute);
app.get('*', (req, res)=>{
res.sendFile(path.join(__dirname, 'build', 'index.html'));
})
app.use(checkDB);
module.exports = app;
Here is the server.js
const http = require('http');
require('dotenv').config();;//declaring the envy file
const app = require('./app');
//mongoDb
const connectDB = require('./db/connect');
const port = process.env.PORT || 5000;
const server = http.createServer(app);
const start = async () => {
try {
await connectDB(process.env.MONGO_URL)
server.listen(port, () =>
console.log(`Server is listening on port ${port}...`)
);
} catch (error) {
console.log(error);
}
};
start();
I don't know how to establish connection to the mongo db for my node JS server in AWS Lambda using serverless on AWS. I've mentioned my question in the handler function below.
The code looks something like this:
import express from "express";
import mongoose from "mongoose";
import dotenv from "dotenv";
import cookieParser from "cookie-parser";
import serverless from "serverless-http";
const PORT = 1234;
dotenv.config();
mongoose.connect(
process.env.MONGO_URL,
() => {
console.log("connected to db");
},
(err) => {
console.log({
error: `Error connecting to db : ${err}`,
});
}
);
const app = express();
app.use(cookieParser());
app.use(express.json());
// this part has various routes
app.use("/api/auth", authRoutes);
app.use((err, req, res, next) => {
const status = err.status || 500;
const message = err.message || "Something went wrong";
return res.status(status).json({
success: false,
status,
message,
});
});
app.listen(PORT, () => {
console.log(`Server listening on port ${PORT}`);
});
export const handler = () => {
// how to connect to mongodb here?
return serverless(app);
};
Here handler is the AWS lambda's handler function. For each http request I'm reading/writing data from/to my DB in some way. After checking the cloudwatch logs, it was clear that the requests sent to the server result in timeout because the connection to mongodb hasn't been established. So how exactly do I use mongoose.connect here?
I tried doing this:
export const handler = () => {
mongoose.connect(
process.env.MONGO_URL,
() => {
console.log("connected to db");
}
);
return serverless(app);
};
But it didn't work possibly because it's asynchronous. So I'm not sure how to make this connection here.
EDIT:
One thing that I realised was that the database server's network access list had only my IP because that's how I set it up initially.
So I changed it to anywhere for now and made the following minor changes:
const connect_to_db = () => {
mongoose
.connect(process.env.MONGO_URL)
.then(() => {
console.log("Connected to DB");
})
.catch((err) => {
throw err;
});
};
app.listen(PORT, () => {
connect_to_db();
console.log(`Server listening on port ${PORT}`);
});
Now I can see "Connected to DB" in the logs but the requests sent still times out after 15 seconds (the timeout limit set currently).
My logs:
What am I doing wrong?
So I did some more digging and asked this around the community. Few things that made me understand what I was doing wrong:
It appeared I wasn't connecting the db and my app response
together. My app was handling the request fine, my db was connecting
fine. But there was nothing tying them together. It's supposed to be simple:
Requests comes in > App waits until db connection has been established > App handles request > App returns response.
Second, calling app.listen was another problem in my code. Calling listen keeps the process open for incoming requests and it ultimately is killed by Lambda on timeout.
In a serverless environment, you don't start a process that listens for requests but, instead, the listening part is done by AWS API Gateway (which I have used to have my Lambda handle http requests) and it knows to send request information to Lambda handler for processing and returning a response. The handler function is designed to be run on each request and return a response.
So I tried adding await mongoose.connect(process.env.MONGO_URL); to all my methods before doing any operation on the database and it started sending responses as expected. This was getting repetitive so I created a simple middleware and this helped me avoid lot of repetitive code.
app.use(async (req, res, next) => {
try {
await mongoose.connect(process.env.MONGO_URL);
console.log("CONNECTED TO DB SUCCESSFULLY");
next();
} catch (err) {
next(err);
}
});
Another important, but small change. I was assigning lambda's handler incorrectly.
Instead of this:
export const handler = () => {
return serverless(app);
};
I did this:
export const handler = serverless(app);
That's it I suppose, these changes fixed my express server on Lambda. If anything I've said is wrong in any way just let me know.
I make two 'supertest' requests one after each other like so:
const request = require('supertest');
const server = require('#bin/www');
it('should do something', async () => {
// prepare data
const data = { ... some data }
// create business
const res = await request(server)
.post('/v2/businesses')
.send({
...data
});
// store the returned business id
const b_id = res.body.data.id;
// now the critical point - to make another http request to the api,
// in order to fetch the business and expect it to be defined.
const res1 = await request(server)
.get('/v2/businesses')
.query({
b_id
});
// expectations...
expect(res.body.data.business).toBeDefined();
});
The test passes on first time, after pressing "Enter" to test again, I got the
following error:
listen EADDRINUSE: address already in use :::3002
The only solution is to kill the whole test process and start it all over again.
The error also occurs on different test suites as well.
This is the command to run the tests using JEST (in the package.json):
"test": "jest --forceExit --detectOpenHandles --watchAll --maxWorkers=1"
But this doesn't work as expected.
There are the imported files from where the server object comes from:
// bin/www
var app = require('#app/app');
var server = require('#app/server');
server.listen(... some callback)
module.exports = app;
// app/app
var app = require('express')();
module.exports = app;
// app/server
var app = require('#app/app');
var server = require('http').createServer(app);
module.exports = server;
As I understand, the server is probably keep running even after the test finishes, then when firing another test, the server status is already active and in use, so therefore this error.
How can I close the server after each test?
I have tried:
afterEach(() => server.close())
But got an error that server.close is not a function.
Just use conditional listen:
// Turn off listen, when you are testing
if (process.env.NODE_ENV !== "test") {
app.listen(process.env.PORT || 4000);
}
Jest using special NODE_ENV, that called test, so you don't need an listen method. Also supertest is providing unical port for every app instance.
I am running unit test with my node project using Jest library everything was working pretty fine. When I created new test for route authentication it starts showing server is already running on port 4000 even I am using afterEach() function to close the serve but don't know why am still getting server is already running on port 4000.
Even I have removed the new test for route authentication and restart my project by closing all the terminals but whenever I run the project it starts showing error that server is already running on port 4000.
Here is the code in user test file where am closing server properly and on the next test file I am again using same functions for server connection and closing.
const request = require("supertest");
const { Genre } = require("../../models/genre");
const { User } = require("../../models/user");
const mongoose = require("mongoose");
let server;
describe("/api/genres", () => {
beforeEach(() => {
server = require("../../index");
});
afterEach(async () => {
server.close();
await Genre.remove({});
});
second test file code
const { User } = require("../../models/user");
const { Genre } = require("../../models/genre");
const request = require("supertest");
describe("auth middleware", () => {
beforeEach(() => {
server = require("../../index");
});
afterEach(async () => {
await Genre.remove({});
server.close();
});
Here is the output of............................
● auth middleware › should return 401 if no token is provided
listen EADDRINUSE: address already in use :::4000
10 |
11 | const port = process.env.PORT || 4000;
> 12 | const server = app.listen(port, () =>
| ^
at Function.listen (node_modules/express/lib/application.js:618:24)
at Object.<anonymous> (index.js:12:20)
at Object.<anonymous> (tests/integration/auth.test.js:6:14)
Test Suites: 1 failed, 3 passed, 4 total
Tests: 1 failed, 26 passed, 27 total
Snapshots: 0 total
Time: 13.606s
Ran all test suites.
Index.js Code
const winston = require("winston");
const express = require("express");
const app = express();
require("./startup/logging")();
require("./startup/routes")(app);
require("./startup/db")();
require("./startup/config")();
require("./startup/validation")();
const port = process.env.PORT || 4000;
const server = app.listen(port, () =>
winston.info(`Listening on port ${port}...`)
);
module.exports = server;
I am not an expert in Node.js, but the nature of your bug is very simple; you're trying to listen on a port that is already being listened on.
This post offers a solid description of require(), which I believe is the source of your trouble.
By calling require("../../index") in each of your test case programs, you are effectively exporting server twice, which results in two app.listen() calls.
You would be infinitely better off exporting your server a single time to some main test program, in which you could then run all your test cases by calling require() on each of the test files. This approach is much better in the long run as well because adding additional test cases would 1000x easier; you could just write a new test script, export it to your main test program, and append it to your list of test cases.
Your test is creating the server (index.js) multiple times. This makes the server trying to listen to the same port in many instances. The Jest documentation reads,
If you have some work you need to do repeatedly for many tests, you
can use beforeEach and afterEach.
It is obvious that you should create the server one time, do all the tests and close it one time. The methods for this are beforeAll and afterAll.
In some cases, you only need to do setup once, at the beginning of a
file. This can be especially bothersome when the setup is
asynchronous, so you can't do it inline. Jest provides beforeAll and
afterAll to handle this situation.
So, your test should look like,
describe("auth middleware", () => {
beforeAll(() => {
server = require("../../index");
});
afterAll(async () => {
await Genre.remove({});
server.close();
});
I am using Mocha + Chai + chai-http to test my server application. The thing is, it needs to do some stuff (mostly DB writes) before actually starting the server. And that crashes my tests, because the tasks that are needed to be run before the server startup are not executed yet. Here's the code that I'm using:
// server declaration, it's just a restify server
(async () => {
await cron.scanDB();
await user.updateEventRoles();
console.log('started');
server.listen(config.port, () => {
log.info('Up and running, %s listening on %s', server.name, server.url);
});
})();
...
module.exports = server;
And in tests:
chai.request(server)
.get('/single/' + eventRes.body.data._id + '/organizers')
.set('X-Auth-Token', 'foobar')
.end((err, res) => {
// some actual tests
What can I do to wait for the server to start before running tests?
The problem is that your server.listen is wrapped in an async function and chai-http does not know to wait for that. One thing you can do is use an event emitter and emit after the server is started.
Then at the top of your test create a before with an async function that returns a new promise that resolves when it receives the server started event.