Express API Test with Jest and Mongoose - node.js

I am working on an API server in express with using mongoose to connect to a MongoDB.
node kicks off index.js which starts an app.js where I do the MongoDB setup and the express setup.
Works fine in normal development, but when I try to use jest to test the APIs I have issues with the mongoose finishing it's connection after the test is complete, and thus jest gives me the following 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 know logically it is due to node.js's async nature. I just don't know how to fix it. I don't know if the solution is restructuring the way I'm running the calls or doing something with promises (which I'm still really weak on).
So how can I fix this issue?
App.js posted below with the connect string altered for public posting.
app.js
import express from 'express';
import path from 'path';
import logger from 'morgan';
import bodyParser from 'body-parser';
import cors from 'cors';
import mongoose from 'mongoose';
import fs from 'fs';
//Bring DB Online
console.log("Brining MongoDB Online")
mongoose.set('debug', true)
const devurl = 'mongodb://mlabuname:mlabpass#ds247170.mlab.com:47170/devdb'
mongoose.connect(devurl, { useNewUrlParser:true }, function() {
console.log('\tMongoDB - Connection has been made');
})
.catch(err => {
console.error('\tApp starting error:', err.stack);
process.exit(1);
});
//Load Models
console.log("\tLoading Mongoose Models")
import User from './models/user'
import Wiki from './models/wiki'
//Express Items
console.log("Brining Express Online")
const app = express();
app.disable('x-powered-by');
app.use(cors());
// View engine setup
app.set('views', path.join(__dirname, '../views'));
app.set('view engine', 'pug');
app.use(logger('dev', {
skip: () => app.get('env') === 'test'
}));
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));
app.use(express.static(path.join(__dirname, '../public')));
// Routes
// Dynamic routes setup by controllers
console.log("\tInstalling Controller Routes")
fs.readdirSync("src/controllers").forEach(function (file) {
if(file.substr(-3) == ".js") {
const route = require("./controllers/" + file)
route.controller(app)
}
})
// Temp ResetDB Route for DEV.
console.log("\tInstalling ResetDB Route")
import resetDB from './routes/resetDB'
app.use('/resetDB', resetDB);
// Default Routes set by the framework, fine for now.
console.log("\tInstalling Default")
import defaultRoutes from './routes/defaultRoutes'
app.use('/', defaultRoutes);
// Catch 404 and forward to error handler
app.use((req, res, next) => {
const err = new Error('Not Found');
err.status = 404;
next(err);
});
// Error handler
app.use((err, req, res, next) => { // eslint-disable-line no-unused-vars
res
.status(err.status || 500)
.render('error', {
message: err.message
});
});
export default app;
Update
I have been pointed to this being a duplicate. I saw that answer and it isn't exactly the same issue. First they are actually testing the model. In this case I am not. I'm simply trying to run the test on the express routes, and the error is showing up after the test ran (and passed).
All that is happening is the system is brining up the MongoDB it isn't doing any transactions to it as part of the test. (As the other question talks about).
Finally in that example they are trying to bring up the mongoose db in the test itself. I'm brining it up as part of the app startup (as it works when I run the routes in dev).

"scripts": {
"test": "jest --watchAll --detectOpenHandles --forceExit"
}
In package.json you can re-write the test script.

Related

Increase body limit for specific route with ExpressJS

We have a REST API built with ExpressJS. One of our routes is getting files as binary data in the JSON request body. Therefore we want to increase the limit of the body.
Simple version of our starting index.js file
const start = async () => {
try {
// Connect to database
await connectDB(process.env.DATABASE_URL);
// Create app
const app = express();
// Body parser
app.use(express.json()); //100kb default
app.use(express.urlencoded({ extended: true }));
// Mount routes
app.use(`/${process.env.API_VERSION}`, authRoutes);
app.use(`/${process.env.API_VERSION}`, profileRoutes);
app.use(`/${process.env.API_VERSION}`, filesRoutes);
app.use(`/${process.env.API_VERSION}`, ticketRoutes);
// Error handler
app.use(errorHandler);
// Not found handler
app.use(notFoundHandler);
// Start server
const server = app.listen(process.env.PORT, () => {
console.log(`Server running in ${process.env.NODE_ENV} mode on http://${process.env.HOST}:${process.env.PORT}/${process.env.API_VERSION}`)
});
} catch(error) {
console.log(error);
}
}
start();
At this point the limit for all routes is 100kb.
Inside our filesRoutes we have 1 specific upload route where we want to increase this limit to 200mb.
import express from 'express';
import { uploadFiles, getFiles, downloadFile, deleteFile } from '../controllers/filesController.js';
import authorize from '../middleware/authorizeHandler.js';
const router = express.Router();
router.get('files', authorize, getFiles);
router.get('files/:id/download', authorize, downloadFile);
router.post('files/upload', [authorize, express.json({ limit: '200mb' })], uploadFile);
router.delete('files/:id', authorize, deleteFile);
export default router;
I added express.json({ limit: '200mb' }) to the upload route but I'm getting an error back request entity too large.
Based on #vighnesh153 comment we could fix the issue by changing app.use(express.json()); to app.use(/^(?!\/api\/files\/upload$)/, express.json()); and add express.json({ limit: '200mb' }) as middleware to the api/files/upload route

Jest and supertest: Test keep exceeding timeout

Hello I am a bit confused by this error I have encountered.
I am working on an Universal React App using Webpack 5 and Express.
I want to implement Jest support by using the React-testing-Library for the frontend (which work) and supertest for the backend (this is where I am blocked).
I am following this basic tutorial recommended by the jest doc himself in order to use jest on an node express environment.
But everytime I get this error:
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."
Here are my code:
server.js
import app from './app.js';
import { mongooseConnection, disconnectMongoDB } from "./routers/services/url/urlDB.js"; // we call mongooseConnect var to connect only once into the mongoDB database
const PORT = process.env.PORT || 8080;
// the server listen on the port set by node on localhost.
app.listen(PORT, () => {
console.log(
`Server listening on \x1b[42m\x1b[1mhttp://localhost:${PORT}\x1b[0m in \x1b[41m${process.env.NODE_ENV}\x1b[0m`,
);
});
// when when we shut down the app we execute a callback function before closing the server
process.on('exit', function() {
disconnectMongoDB();
});
app.js
import express from 'express';
import path from 'path';
import cors from 'cors';
import {envIsProduction, envIsDevelopment} from './envmode/envUtil.js';
import { enableHMR } from './reload/hotReload.js';
let app = express();
// if we have set the environnent on production then:
if (envIsProduction()) {
console.log(" _______________________________________ ");
console.log("| |");
console.log("| ( PRODUCTION ) |");
console.log("|_______________________________________|");
console.log(" ");
app.use(express.static(path.join(__dirname,'../client'))); // we serve static file like the bundle-app.js to the browser from the current directory where the server is executed and we move to the top root to access the file
}
else if (envIsDevelopment()) {
console.log(" _______________________________________ ");
console.log("| |");
console.log("| ( DEVELOPMENT ) |");
console.log("|_______________________________________|");
console.log(" ");
enableHMR(app); // we enable the Hot MPodule Reload on the frontend and the backend
}
app.use(cors());
app.use(express.urlencoded({extended:false}));
app.use(express.json());
//Hot reload!
//ALL server routes are in this module!
app.use((req, res, next) => {
require("./routers/routers")(req, res, next);
});
export default app;
routers.js
import renderPage from "./renderpage/renderPage.js";
import { serverRoutes, reactRouterRoutes, getReactRouterRoutesString } from "./routes.js";
import express from "express";
import routerLoginDB from "./request/routerLoginDB.js";
import routerSignupDB from "./request/routerSignupDB.js";
const router = express.Router();
// Put all your server routes in here
// When the user connect to the root of the server we send the page
router.get(serverRoutes.root, renderPage);
// When the user send a get request by the /click route a console.log and a respone is send.
router.get(serverRoutes.click, (req, res)=>{
res.status(200).send("Click");
});
// when this user want to login into his account, we ask for the routerLoginDB to handle it
router.post(serverRoutes.login,routerLoginDB);
// when this user want to signup into his account, we ask for the routerSignupDB to handle it
router.post(serverRoutes.signup, routerSignupDB);
// For all the routes that only react-router need to use, if we refresh on a nested route of the react-router from the client side then we redirect it to the root route "/"
router.get(reactRouterRoutes,(req,res) => {
res.redirect("/");
});
router.get("*", (req,res) =>{
res.status(404).send('page not found');
}); //For all other type of request excluding the one specified here, we send back a 404 page;
module.exports = router;
app.test.js
import request from '../utils/test-node-utils.js'
describe("Test the /click path", () => {
test("It should response the GET method", () => {
return request
.get("/click")
.expect(200);
});
});
and finally test-node-utils.js
import supertest from "supertest";
import app from "../serverside/app.js";
const request = supertest(app);
export default request;
Don't believe what the error say because I think it is more deep than that.
I have tried to increased the jest timeout value but it keep being stuck and reach the timeout limit.
I have done exactly like the tutorial say without using my project structure and it worked but when I try to implement the tutorial in my backend structure, it don't work with supertest.
I think it is related to my files or backend structure that make it don't work with the test.
Thanks in advance for your help
I've recently debugged a similar issue where my Jest tests would run successfully (or not) in my dev. environment but when I would try and package the app as a Docker image all my tests would time out.
It turned out that by commenting out the line which setup CORS, which for me I only turned on in production builds (should have been a clue), the tests started to run again when building the image.
...
const NODE_ENV = process.env.NODE_ENV;
const app = express();
NODE_ENV.toUpperCase() === 'PRODUCTION' && app.use(cors);
...
I mentioned this as I can see from your snippet above that you are also using the cors middleware and that, in your case, it's set all the time.
Perhaps not your issue, and you may want CORS in your tests for some reason, but try commenting it out and see if your tests run.

Trying to deploy my Firebase cloud function that uses an expressjs server to render stuff and it's telling me ESM isn't supported?

import functions from "firebase-functions";
import createError from 'http-errors'
import express from "express";
import path from "path";
import cookieParser from "cookie-parser"
import logger from "morgan";
import url from 'url';
const __dirname = path.dirname(new url.URL(import.meta.url).pathname);
const viewsDir = path.join(__dirname, './', 'views/');
const app = express();
app.set('views', path.join(viewsDir));
app.set('view engine', 'pug');
app.use(logger('dev'));
app.use(express.json());
// use the qs library for urlencoded strings for structuring urlparams
app.use(express.urlencoded({extended: false}));
app.use(cookieParser());
// we're serving the VIEWs already but now we need to serve the static files
// css n stuff
app.use(express.static(path.join(__dirname, '../', 'public')));
// TECHNICALLY I should move this to a new file, and I will at some point
// when the routes get more complicated.
const router = express.Router();
router.get('/*', (req,res,next)=>{
res.render('index', {title: 'express '})
});
// use that router, boii
app.use('/', router);
// throw dem errors boiii
app.use((req,res,next)=>{
next(createError(404));
});
app.use(function(err, req, res, next) {
// set locals, only providing error in development
res.locals.message = err.message;
res.locals.error = req.app.get('env') === 'development' ? err : {};
// Warn that noise in console cuz life is chaos.
console.warn(err)
// render the error page
res.status(err.status || 500);
res.render('error');
});
const fapp = functions.https.onRequest(app);
export {fapp, app};
I'm exporting fapp and app separately because local server reasons.
both my project and my functions package.json use "type": "module"
The firebase documentation/changelog says that they're using esm as the default for browsers so like... esm is supported. Right? Because it's saying it's not.
What's even weirder is that also in my firebase console one of the messages says
"Detailed stack trace: Error [ERR_REQUIRE_ESM]: Must use import to load ES Module: /workspace/index.js"
"require() of /workspace/index.js from /layers/google.nodejs.functions-framework/functions-framework/node_modules/#google-cloud/functions-framework/build/src/loader.js is an ES module file as it is a .js file whose nearest parent package.json contains "type": "module" which defines all .js files in that package scope as ES modules."
but... that's not even my code- thats some google stuff.
What's EVEN weirder is that my site IS working, but it doesn't work on refresh -- like site.com/ works fine and site.com/subdirectory works fine because it's an spa react app, but if i refresh the page on site.com/subdirectory it doesn't even provide MY 404 page... which is what this cloud function / expressjs app is supposed to fix.
It works fine locally. The express server handles that situation correctly.
So... does anyone know what's up with this or had a similar experience? I really like firebase and I don't wanna change but if I can't use ESM... like... it's 2021.

Express app won't let me check what's inside req

I know my problem may seem not very specific, but I'm having problem describing what's happening because I don't understand it :(
So I've written small express app with ssr (for react) and jwt authentication. The SSR part works nice but the rest is crap.....
import 'babel-polyfill';
import express from 'express';
import bodyParser from 'body-parser';
import logger from 'morgan';
import authRouter from './auth/authRouter';
const app = express();
app.use(express.static('public'));
app.use(logger('dev'));
app.use(bodyParser.json({ type: '*/*' }));
app.use(bodyParser.urlencoded({ extended: false }));
//a lot of irrelevant ssr code
authRouter(app);
app.listen(3000, function(err) {
if (err) {
console.log(err);
}
console.log('main server on port 3000');
});
This is my main server file. My first problem is I don't see ANY console.logs from my files. There's nothing in my terminal. That's the reason I can't see how does my requests look like in my app. I'm testing it using postman like that:
And that's the authRouter I'm using above in main server file:
import express from 'express';
import { signup, signin } from '../controllers/authentication';
import { jwtLogin, localLogin } from '../services/passport';
import passport from 'passport';
passport.use(jwtLogin);
passport.use(localLogin);
//if user is auth'd do not create session for him
const requireAuth = passport.authenticate('jwt', { session: false });
const requireSignin = passport.authenticate('local', { session: false });
const authRouter = function(app) {
app.get('/auth', requireAuth, function(req, res) {
res.send({ hi: 'there' });
});
app.post('/auth/signin', requireSignin, signin); // irrelevant right now
app.post('/auth/signup', signup);
};
export default authRouter;
And that's signup function I'm using in router:
const signup = (req, res, next) => {
console.log('reqqqqqqq', req);
const { email, password, passwordCheck } = req.body; //code crashes here
//some code I deleted for everyones convenience
};
Every request I make my app crashes because req.body is undefined. I can't log req because I can't see any logs. I also tried sending back stringified version of my request body but every time i get "TypeError: Converting circular structure to JSON".
I'll be happy to add any information you may need
EDIT:
I'm gonna check that later at home but now I'm thinking there is something wrong with ssr part of my app because I don't even see that 'main server on port 3000' log..... At the same time server responds with right html, js files and routing works well so.....anyway I'm gonna look it up later
Try using util.inspect from node:
const util = require('util');
// usage in your code
const signup = (req, res, next) => {
console.log(util.inspect(req, { showHidden: true, depth: null }));
const { email, password, passwordCheck } = req.body;
...
};
I solved it.....unexpectedly the problem was lying in my package.json file.
My start script looked like this:
"scripts": {
"start": "nodemon build/server.bundle.js | nodemon build/api.bundle.js",
...
}
And because of that I had these weird errors. Only api.bundle.js file was running correctly......
Anyway thanks for your help ;)

Express route in controller not resolved

I'm using express 4.16.3 and trying to make sense of why one request to a controller works and a request doesn't.
in my server.js i've got the following. There's no semi-colons because used prettier beforehand.
import express from 'express'
import bodyParser from 'body-parser'
import cors from 'cors'
import PriceCheckerController from './controllers/PriceChecker'
import PersonalLibraryController from './controllers/PersonalLibrary'
const app = express()
app.set('port', process.env.PORT || 5000)
app.use(bodyParser.json())
app.use(bodyParser.urlencoded({extended: true}))
app.use(cors())
// routes definition
app.use('/api/books',PersonalLibraryController) // this does not
app.use('/api/stock-prices', PriceCheckerController) // this works
//
app.listen(app.get('port'), error => {
if (error) {
logger.error(`error fcc-isqa:${error}`)
} else {
logger.info(`fcc-isqa is running on port ${app.get('port')}`)
}
})
export default app
In PriceCheckerController i've implemented it like so.
import 'babel-polyfill' // mocha required
import express from 'express'
import logger from '../logger'
const PriceCheckerController = express.Router()
PriceCheckerController.use((req, res, next) => {
logger.info(
`date=>${new Date()}\n method=>${req.method}nsender:${req.ip}`
)
})
PriceCheckerController.get('/', async (req, res) => {
return res.status(200).json({message:'soon'})
})
export default PriceCheckerController
In PersonalLibraryController i've implemented it like so
import 'babel-polyfill'
import express from 'express'
import logger from '../logger'
const PersonalLibraryController = express.Router()
PersonalLibraryController.use((req,res,next)=>{
logger.info(
`library date=>${
new Date()}method=>${req.method}url=>${req.baseUrl}${req.path}`
)
})
PersonalLibraryController.route('/test')
.get(async (req, res) => {
return res.status(200).json({message: 'get soon'})
})
.post(async (req,res)=>{
return res.status(200).json({message: 'post soon'})
})
export default PersonalLibraryController
A request to /api/stock-prices returns ok with message soon.
A request to /api/books/test is logged by the middleware but a response is not sent back. It eventually gives a timeout
Can anyone give me any insights/ help in understanding what is the problem and how to fix it?
Thanks in advance.
Your middleware functions need to call next() to carry on the route execution.
e.g
PriceCheckerController.use((req, res, next) => {
logger.info(
`date=>${new Date()}\n method=>${req.method}nsender:${req.ip}`
)
next();
})
https://expressjs.com/en/guide/using-middleware.html
"If the current middleware function does not end the request-response cycle, it must call next() to pass control to the next middleware function. Otherwise, the request will be left hanging."

Resources