import runs all code from module instead of just the imported function - node.js

This is my index.js file, located in the ./src directory:
import { MongoClient } from "mongodb";
import CharacterDAO from "./dao/character";
import GearDAO from "./dao/gear";
import { startServer } from "./server";
import { seedData } from "./dataSeed";
// connect mongoDb, seed data if needed, run fastify server
export const runServer = async ({ dbUrl, dbName, environment, port }) => {
// test seed data when starting server if running a test suite
if (environment === "test") {
await seedData({
hostUrl: dbUrl,
databaseName: dbName
});
}
await MongoClient.connect(dbUrl, {
poolSize: 50,
useNewUrlParser: true,
useUnifiedTopology: true,
wtimeout: 2500
})
.then(async conn => {
const database = await conn.db(dbName);
// inject database connection into DAO objects
CharacterDAO.injectDB(database);
GearDAO.injectDB(database);
// start the fastify server
startServer(port);
})
.catch(err => {
console.log(err.stack);
// process.exit(1);
});
};
const serverArguments = process.argv.slice(2).map(arg => {
return arg.split("=")[1];
});
const serverOptions = {
dbUrl: serverArguments[0],
dbName: serverArguments[1],
environment: serverArguments[2],
port: serverArguments[3]
};
runServer({
...serverOptions
});
jestconfig.json
{
"transform": {
"^.+\\.(t|j)sx?$": "ts-jest"
},
"testEnvironment": "node",
"testRegex": "(/__tests__/.*|(\\.|/)(test|spec))\\.(jsx?|tsx?)$",
"moduleFileExtensions": ["ts", "tsx", "js", "jsx", "json", "node"]
}
Test script from package.json used to run the test (db credentials are omitted)
"test": "dbUrl=mongodb+srv://sdaw-dsawdad-dsadsawd#cluster0-jopi5.mongodb.net dbName=untitled-combat-game-test environment=test port=4000 jest --config jestconfig.json"
My test file:
import { runServer } from "../index";
beforeAll(async () => {
const serverOptions = {
dbUrl: process.env.dbUrl,
dbName: process.env.dbName,
environment: process.env.environment,
port: process.env.port
};
console.log(serverOptions);
await runServer({
...serverOptions
});
});
describe("mock test", () => {
it("should run a basic test", () => {
expect(true).toBe(true);
});
});
What happens when I run the test:
the test script runs runServer
the index.js file runs runServer
This causes a invalid URI error (since the process.argv referenced in index.js does not include a valid mongodb URI). I double-checked this by commenting out the runServer call at the bottom of my index.js file - and everything runs fine.
Moving the runServer function to a different file and importing it from there also solves the issue. So importing in both index.js and the test file does not result in multiple calls.
What am I doing wrong?

Importing/requiring a file evaluates the code inside of it (read: runs the code inside of it). You're not technically doing anything wrong, but for the purpose of your tests the code as you have written it won't work.
In your index.js file you are executing runServer(). Whenever that file is imported/required, that function call is also run.
Having a start.js file or similar which will actually start your server is a common pattern. This will help you avoid the issue you're experiencing.

I would split the definition of your server and invoking your server into two different files, say server.js and index.js. I will leave the fixing up of the imports to you, but this is the idea:
server.js
// connect mongoDb, seed data if needed, run fastify server
export const runServer = async ({ dbUrl, dbName, environment, port }) => {
// test seed data when starting server if running a test suite
if (environment === "test") {
await seedData({
hostUrl: dbUrl,
databaseName: dbName
});
}
await MongoClient.connect(dbUrl, {
poolSize: 50,
useNewUrlParser: true,
useUnifiedTopology: true,
wtimeout: 2500
})
.then(async conn => {
const database = await conn.db(dbName);
// inject database connection into DAO objects
CharacterDAO.injectDB(database);
GearDAO.injectDB(database);
// start the fastify server
startServer(port);
})
.catch(err => {
console.log(err.stack);
// process.exit(1);
});
};
index.js
import { runServer } from './server';
const serverArguments = process.argv.slice(2).map(arg => {
return arg.split("=")[1];
});
const serverOptions = {
dbUrl: serverArguments[0],
dbName: serverArguments[1],
environment: serverArguments[2],
port: serverArguments[3]
};
runServer({
...serverOptions
});

Related

What is the correct way to make MongoDB Atlas connection available in middleware functions in Node.js/Express?

Environment
Node.js (18 LTS) / Express (^4.18.2)
MongoDB Native Driver (^4.12.0)
MongoDB Atlas (5.0.14)
Application Structure
.github
config
- mongodb_client.js
dist
middleware
node_modules
routes
src
views
.env
.gitignore
app.js
package.json
README.md
Connection Code
As a sanity check, this is the connection code that is provided in the MongoDB Atlas interface:
As a screenshot:
As code:
const { MongoClient, ServerApiVersion } = require('mongodb');
const uri = "mongodb+srv://admin:<password>#cluster0.******.mongodb.net/?retryWrites=true&w=majority";
const client = new MongoClient(uri, { useNewUrlParser: true, useUnifiedTopology: true, serverApi: ServerApiVersion.v1 });
client.connect(err => {
const collection = client.db("test").collection("devices");
// perform actions on the collection object
client.close();
});
Desired Behaviour
The code snippet provided in the MongoDB Atlas interface performs the connection and subsequent database calls in one file.
However, I would like to:
Create a file that contains the MongoDB Atlas connection (e.g mongodb_client.js)
Export the connection so that it can be used in middleware files (e.g my_middleware_01.js)
So, in pseudo code, I imagine it would look something like this:
config / mongodb_client.js
import { MongoClient, ServerApiVersion } from 'mongodb';
const connection_string = process.env.MONGODB_CONNECTION_STRING;
const mongodb_client = new MongoClient(connection_string, { useNewUrlParser: true, useUnifiedTopology: true, serverApi: ServerApiVersion.v1 });
// export the connection somehow
export { mongodb_client };
middleware / my_middleware_01.js
import { mongodb_client } from '../config/mongodb_client.js';
const api_myResource_get = async (req, res) => {
mongodb_client.open();
let collection = mongodb_client.db('myDatabase').collection('myCollection');
let result = await collection.findOne(query, options);
res.json({ result: result });
mongodb_client.close();
};
export { api_myResource_get };
What I've Tried
It seems I was grappling with this dynamic over a year ago and posted my solution here:
https://stackoverflow.com/a/70135909
However, I think that conventions have changed since then.
For example when instantiating the client, the current method seems to be:
const client = new MongoClient(connection_string, { useNewUrlParser: true, useUnifiedTopology: true, serverApi: ServerApiVersion.v1 });
whereas previously it was something like:
await MongoClient.connect(connection_string);
I've Google searched:
how to make mongodb connection available in node.js middleware?
But all the results seem to reference this older convention and I'd like to ensure I am using best practice (and most recent conventions).
Related Questions and Resources
Passing Mongo DB Object DB to Express Middleware
What is best way to handle global connection of Mongodb in NodeJs
How do I manage MongoDB connections in a Node.js web application?
What is the difference between MongoClient and the client object which we get in the callback of MongoClient.connect() method
How to properly reuse connection to Mongodb across NodeJs application and modules
MongoDB Driver Connection Documentation
EDIT 01:
Below is one attempt which is returning the error:
TypeError: Cannot read properties of null (reading 'db')
config / mongodb_connection.js
import { MongoClient, ServerApiVersion } from 'mongodb';
const connection_string = process.env.MONGODB_CONNECTION_STRING;
class mongodb_connection {
static async open() {
if (this.conn) return this.conn;
this.conn = await MongoClient.connect(connection_string, { useNewUrlParser: true, useUnifiedTopology: true, serverApi: ServerApiVersion.v1 });
return this.conn;
}
}
mongodb_connection.conn = null;
mongodb_connection.url = connection_string;
export { mongodb_connection };
middleware / api_mongodb_get.js
import { mongodb_connection } from '../../config/mongodb_connection.js';
const api_mongodb_get = async (req, res) => {
try {
mongodb_connection.open();
const collection = mongodb_connection.conn.db('pages').collection('pages');
const result = await collection.findOne({ "my_key": "my value" });
res.json({ data: result });
mongodb_connection.close();
} catch (error) {
console.error(error);
res.status(500).send(error);
}
};
export { api_mongodb_get };
EDIT 02:
The following 'works' but I don't know if it is best practice or not.
In other words, I don't know if I am overlooking something that will cause undesired behavior.
config / mongodb_connection.js
import { MongoClient, ServerApiVersion } from 'mongodb';
const connection_string = process.env.MONGODB_CONNECTION_STRING;
const mongodb_connection = new MongoClient(connection_string, { useNewUrlParser: true, useUnifiedTopology: true, serverApi: ServerApiVersion.v1 });
export { mongodb_connection };
middleware / api_mongodb_get.js
import { mongodb_connection } from '../../config/mongodb_connection.js';
const api_mongodb_get = async (req, res) => {
try {
mongodb_connection.connect(async err => {
const collection = mongodb_connection.db('pages').collection('pages');
const result = await collection.findOne({ "my_key": "my value" });
res.json({ data: result });
mongodb_connection.close();
});
} catch (error) {
console.error(error);
res.status(500).send(error);
}
};
export { api_mongodb_get };
Insetead of using mongodb, use mongoose library to establish the connection.
Here is an example to establish the connection with mongodb cluster:
connectDb.js:
const dotenv = require('dotenv').config();
const DB_URL = process.env.DB_URL;
const mongoose = require('mongoose');
const connectDb = async () => {
try {
const connection = await mongoose.connect(DB_URL)
console.log(`Connected to database Successfully: ${connection}`)
} catch (error) {
console.log(error)
}
}
module.exports = connectDb;
and I think I don't need to mention that the DB_URL is the URL which is provided by the mondodb cluster.

How to deep mock Prisma Client in JavaScript (not TypeScript)?

I'm want to unit test Next.js API route with Prisma in JavaScript. Unfortunately, the Prisma's unit testing guide is written for Typescript.
I have jest.config.js which will setup the mock in jest.setup.js
const nextJest = require("next/jest");
const createJestConfig = nextJest({
dir: "./",
});
const config = {
setupFilesAfterEnv: ["<rootDir>/jest.setup.js"],
moduleDirectories: ["node_modules", "<rootDir>/"],
moduleNameMapper: {
"#/(.*)$": "<rootDir>/$1",
},
};
module.exports = createJestConfig(config);
and this is how I configured the mock in jest.setup.js
import prisma from "#/utils/client";
jest.mock("#/utils/client", () => ({
__esModule: true,
default: {
user: {
findMany: jest.fn(),
},
// ... and each and everyone of the entities
},
}));
export const prismaMock = prisma;
and the following test case passed
describe("Calculator", () => {
it("renders a calculator", async () => {
await prismaMock.user.findMany.mockResolvedValue(["abc]);
const { req, res } = createMocks();
await handler(req, res);
expect(res._getStatusCode()).toBe(200);
expect(res._getData()).toBe('["abc"]');
});
});
With this approach, I have to mock each and everyone of the models and function in jest.setup.js. Is there a way to mock all the models and functions automatically? Is there a similar JavaScript library which provides mockDeep from jest-mock-extended?

How to turn of module reloading in JEST?

I am using Jest to testing express REST api written in express.js and typescript using ts-jest. My problem is Jest loads app module (express) in every test suite, it lasts only 3-4 seconds, but there is ~80 test suites each containing a multiple test cases.
My first thought was to remove jest.resetModules() from global afterAll() function, but it didn't helped. Is there another way how to change this behaviour or it is feature by design?
setup.ts
import { sequelize } from '../src/db/models'
import seeds from '../src/db/seeders/test'
// multiple mocks of services
jest.mock('../some/custom/module/mocks')
beforeAll(async () => {
await sequelize.sync({ force: true }) // basically drop db and create clean one
await seeds() // seed database with data
})
afterAll(async () => {
jest.clearAllMocks()
// jest.resetModules() //! This line was removed
await sequelize.close()
global.gc()
})
jest.setTimeout(10000)
global.ts
import { createJwt } from '../src/utils/authorization'
export default async () => {
// create some acces tokens and add to process.env
process.env.jwttoken = await createJwt({ uid: 1 }, { audience: 'users' })
}
jest.config.js
module.exports = {
transform: {
'^.+\\.ts?$': 'ts-jest'
},
roots: [
'<rootDir>/tests/'
],
moduleFileExtensions: [
'ts',
'js'
],
setupFilesAfterEnv: [
'<rootDir>/tests/setup.ts'
],
globalSetup: '<rootDir>/tests/global.ts',
testEnvironment: 'node'
}
example test
import supertest from 'supertest'
import app from '../../../../../src/app'
describe(`[GET] ${endpoint(':id')})`, () => {
const request = supertest(app). // every time jest hits this line in test, it load app again
it('no authorization token | code 401', async () => {
const response = await request.get('/something')
.set('Content-Type', 'application/json')
expect(response.status).toBe(401)
})
start script
POSTGRESQL_URL=postgresql://postgres:root#127.0.0.1:5432/database JWT_SECRET=secret node --expose-gc \"./node_modules/jest/bin/jest.js\" --runInBand --passWithNoTests --logHeapUsage

Typeorm Database not starting?

I bootstrapped typeorm with option init typeorm --database postgres --express.
I changed ormconfig to match my info in database.
There's no error. But it seems like nothing is running.
It seems that my database isn't the problem, as it does not throw the error.
Anything I am missing?
Below is the default setting
// index.ts
import 'reflect-metadata';
import { createConnection } from 'typeorm';
import * as express from 'express';
import * as bodyParser from 'body-parser';
import { Request, Response } from 'express';
import { Routes } from './routes';
import { User } from './entity/User';
createConnection()
.then(async (connection) => {
// create express app
const app = express();
app.use(bodyParser.json());
// register express routes from defined application routes
Routes.forEach((route) => {
(app as any)[route.method](
route.route,
(req: Request, res: Response, next: Function) => {
const result = new (route.controller as any)()[route.action](
req,
res,
next
);
if (result instanceof Promise) {
result.then((result) =>
result !== null && result !== undefined
? res.send(result)
: undefined
);
} else if (result !== null && result !== undefined) {
res.json(result);
}
}
);
});
// setup express app here
// ...
// start express server
app.listen(8080);
// insert new users for test
await connection.manager.save(
connection.manager.create(User, {
firstName: 'Timber',
lastName: 'Saw',
age: 27,
})
);
await connection.manager.save(
connection.manager.create(User, {
firstName: 'Phantom',
lastName: 'Assassin',
age: 24,
})
);
console.log(
'Express server has started on port 3000. Open http://localhost:3000/users to see results'
);
})
.catch((error) => console.log(error));
I think you didn't config your database details. You have to add ormconfig file to your project.
Step 1 :- Create a file called ormconfig.ts in root directory.
Step 2:- Add these below code to that file and put your database details here.
{
"type": "postgres",
"host": "localhost",
"port": 3306,
"username": "root",
"password": "",
"database": "test",
"synchronize": true,
"entities": ["here put paths for your entities"],
}
First you have to config your typeorm configurations using ormconfig file.

Knex pool full on migration

I'm trying to get started with knex.js and I can't get migrations to work. Knex works fine for my API calls. Here's my setup:
knexfile.js
const env = process.env;
module.exports = {
client: 'mysql',
connection: {
host: env.DB_HOST,
database: env.DB_NAME,
user: env.DB_USER,
password: env.DB_PASSWORD,
port: env.PORT
},
pool: {
min: 0,
max: 50
},
migrations: {
directory: './db/migrations',
tableName: 'knex_migrations'
},
seeds: {
directory: './db/seeds'
}
};
knex.js
const config = require('../knexfile.js');
module.exports = require('knex')(config);
events.js
const express = require('express');
const router = express.Router();
const knex = require('../../db/knex.js');
// GET api/events
router.get('/', (req, res) => {
knex('events')
.then(events => { res.send(events) }
.catch(err => { console.log(err); })
});
module.exports = router;
and then I have a file in the migrations folder with:
exports.up = function(knex) {
return knex.schema.createTable('users', function (t) {
t.increments('id').primary()
t.string('username').notNullable()
t.string('password').notNullable()
t.timestamps(false, true)
}).then(() => { console.log('created users table') })
.catch((err) => { throw err} )
.finally(() => { knex.destroy() })
};
exports.down = function(knex) {
return knex.schema.dropTableIfExists('users')
};
When I run knex migrate:latest I get TimeoutError: Knex: Timeout acquiring a connection. The pool is probably full. Are you missing a .transacting(trx) call?
I know similar questions have been asked before, but I can't seem to find any that shed light on my particular situation. I've tried adding a knex.destroy() to the end of my GET request but that doesn't seem to help (it just makes the connection unusable if I add other request handlers below).
I did try checking the knex.client.pool in a finally clause at the end of the GET request. numUsed was 0, numFree was 1, numPendingAcquires and numPendingCreates were both 0. I do find it odd that numFree was only 1 given that my knexfile specifies max 50. Any advice greatly appreciated.
Following #technogeek1995's comment, the answer turned out to be adding require('dotenv').config({path: '../.env'}); to knexfile.js (in retrospect, this part seems obvious), and running the cli from the same directory. Hope this helps someone else.

Resources