Best way to setup files for jest and mongodb - node.js

Hihi.
I'm having a problem where jest it's sometimes failing a couple of tests randomly, most of the time because of this error "mongodb memory server cannot perform operation: a background operation is currently running for collection".
In another post I read something about building differents mongo instance for each block of tests.
What I have so far is a globalsetup file where I start the mongo replica set like this:
// global.ts
import { MongoMemoryReplSet } from "mongodb-memory-server";
const replSet = new MongoMemoryReplSet({
replSet: { storageEngine: "wiredTiger" },
});
module.exports = async () => {
await replSet.waitUntilRunning();
const uri = await replSet.getUri();
process.env.MONGODB_URI = uri;
};
and my db.ts is like this
// db.ts
export const connect = async () => {
mongoose.set("useFindAndModify", false);
const conn = mongoose.connect(
process.env.MONGODB_URI || config.connectionString, connectionSettings
);
When trying to call it from a test file I do something like this
// test.spec.ts
import db from "./db";
beforeAll(async () => {
await db.connect();
});
afterAll(async (done) => {
await db.dropCollections();
await db.disconnect(done);
});
beforeEach(async () => {
await seed();
});
describe('Some test', () => {
it('Should not fail and get the seeders', () => {
// some random tests using the seeds values
})
})
What I read in that post is instead of using globalSetup use a setupFile that will run for every test instead of one globally and then I MIGHT be able to solve the concurrency issue I have with my tests.
So, to conclude, does anyone knows if there is a proper way to configure the mongodb in memory or if I am doing something THAT BAD that is allowing this to happend or if is there any kind of improvement I can do that will prevent "mongodb memory server cannot perform operation: a background operation is currently running for collection" this to happen?

Related

How can I either wait or prevent jest from running my db connection in Sequelize, so as to stop the error "Cannot log after tests are done..."

Im trying to add unit tests to my node express app. The app uses sequelize with sqlite as its orm/database. I am trying to unit test one of my simplest controllers directly (not even trying to test routes with supertest yet)
I was able to get one basic successful test, the issue I a having is that since my controller imports my sequelize User model, it also imports the instantiation of Sequelize. This results in sequelize trying to connect to the DB while the tests are performed. The test is executed before all the db stuff happens, and hence at the end my result is a lot of "Cannot log after tests are done" because Sequelize is still trying to connect and log things, some from the sequelize module itself and others from my code connecting and sync()ing the db.
If I remove the import and usage of the model in my controller, I no longer have those issues, so clearly importing the model is causing all those calls to initialize sequelize. How can i ask Jest to either wait until all async processes are done, or just prevent sequelize from initializing at all (I dont actually need a db connection, i will test them all with mocks)
So here is what the test looks like
describe('testing user registration', () => {
let mRes: Response<any, Record<string, any>>;
let mNext;
beforeAll(()=>{
mRes = {
status: jest.fn().mockReturnThis(),
json: jest.fn()
}
mNext = jest.fn(err => err)
jest.spyOn(UserModel, 'create').mockResolvedValue(createdUser)
})
afterAll(()=>{
jest.resetAllMocks();
})
test('with invalid email', async () => {
const result = await registerUser(mReqInvalidEmail, mRes, mNext);
expect(mNext).toBeCalledWith(invalidBodyError)
})
})
Here is what the Controller looks like:
const registerUser = async(req:Request,res:Response,next:NextFunction) : Promise<Promise<Response> | void> => {
const {body} = req
const {email, role} = body;
if(!isValidEmail(email) || !isValidRole(role)){
const error = createError(
400,
'Invalid body, please provide a valid email address a role of oneOf[\"user\",\"super\"]',
{
body: {
email: 'a valid email string',
role: 'string, oneOf [user, super]'
}
}
);
return next(error);
}
const password = generatePassword()
const hash = hashPassword(password)
const user = await User.create({email, role, password:hash}).catch((err: Error) : Error => {
return createError(500, 'woopsie', err)
})
if(user instanceof Error){
return next(user)
}
return res.status(200).json({
email,
role,
password
});
}
The model:
import {sqlzDB} from "../../database/db";
const User = sqlzDB.define('User',
{
...myfields, not including to save space
}, {
options
})
And finally, where sequelize gets initialized (declaring sqlzDB). This is all the code that is running that I need to either wait for it to finish, or just prevent it from getting called at all!
const {Sequelize} = require('sequelize');
export const sqlzDB = new Sequelize({
dialect: 'sqlite',
storage: 'database/db.sqlite'
});
sqlzDB.authenticate()
.then(():void => {
console.log('Connection to database established successfully.');
}).catch((err : Error): void => {
console.log('Unable to connect to the database: ', err);
})
sqlzDB.sync().then(()=>{
console.log('Sequelize Synced')
})
My test passes just fine. Note that for the test i wrote i dont actually need the mocks yet, Since im just trying to get the setup to work correctly.
I have tried suggestions I have seen out here like calling await new Promise(setImmediate); after my test, or also closing the connection with sqlzDB.close() which causes some different issues (it closes the connection but still tries to log)
So i dont know how to approach this at this point! Sorry for th elong question and thanks to whoever took their time to read this!
You can try a trick to avoid this issue: Close the connection in afterAll
afterEach(() => { // reset mock should be in afterEach
jest.resetAllMocks();
});
afterAll(async ()=>{
await sqlzDB.close();
await new Promise(res => setTimeout(res, 500)); // avoid jest open handle error
});

Jest run code before and after all of the tests

I would like to run code before and after all the tests, not file wide, test wide. For example;
Before starting the e2e tests, I would like to run the server in test mode with the database. After all, I would like to flush my db and close the processes.
I don't know if it is possible but I also would like to have a global db variable to do tests. What I am currently doing is like this:
describe("Posts Module", () => {
let dbService: DatabaseService;
beforeAll(async () => {
dbService = new DatabaseService();
await dbService.init();
});
it("should give the posts", () => {
supertest(app)
.get("/posts")
.expect(200)
.then(async (response) => {
const dbPosts = await dbService.getPosts();
expect(response.body).toBeDefined();
expect(response.body.posts).toEqual(dbPosts);
});
});
afterAll(async () => {
await flushDb(dbService);
await dbService.close();
});
});
But what I actually want is initing this database service only once before all of the module tests (also starting the server, currently I start the server manually and run the tests afterwards).

Wait for DB initialization before running Jest tests

I have to add some features, and their corresponding tests, to an API backend I developed some months ago using NodeJS. Checking the test cases I wrote back then, I've come across with this code:
index-route.spec.js
const app = require('../../app');
const request = require('supertest');
function delay() {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve();
}, 3000);
});
}
beforeAll(async () => {
await delay();
});
// START TESTS
The reason of this delay is to give time to the application to initialize the database connection (I use the Sequelize ORM). This is done in a file (db.js) required from the application main file (app.js):
db.js
async function run() {
try {
// Creates database if it doesn't exist
await DB_init();
// Test database connection
await DB_test();
// Connection was OK, sync tables and relationships
await DB_sync();
// Check if the DB is empty and, in this case, fill the basic data
await DB_init_values();
} catch(error => {
log.error("Couldn't connect to the database '"+dbName+"':", error);
process.exit(1);
}
}
// Init the DB
run();
This works, but now that I have to add more tests, I'd like to refactor this code to avoid the added artificial delay. How could I wait for the database initialization code to be finished before starting the tests? It seems that it's not possible to require a module synchronally, is it?
Cheers,

Possible collisions of asynchronous unit tests against a test database in Mocha

I am new to the javascript ecosystem, and I am trying to wrap my head around a decent way to do some basic unit testing against the REST API using a stored set of fixtures, i.e. .json files.
My entry point sets up a testing environment by connecting to a test database and emits a ready state.
if (!process.env.TESTING) {
/* Connect to MongoDB */
connect(process.env.DEV_DB_HOST, process.env.DEV_DB, () => app.emit(ready));
app.on(ready, () => {
app.listen(port, () => console.log(`Listening on port ${port}!`));
});
} else {
connect(process.env.TEST_DB_HOST, process.env.TEST_DB, () => app.emit(ready));
}
export default app;
Once the connection is established, the collection recreated in the test database using fixtures and the tests are executed.
import Paper from '../models/paper.js';
describe('Home Page', () => {
before(async() => {
await new Promise((resolve, reject) => {
app.on(ready, () => resolve());
}).then(async() => {
try {
await Paper.collection.drop();
} catch(error) {
/* Mongo throws an exception if the collection in question does not exist */
}
await Paper.create(fixtures);
});
});
after(async() => {
await Paper.collection.drop();
});
it('Returns a list of items ordered by date in descending order', async() => {
const response = await chai.request(app).get('/');
expect(response.body).to.have.lengthOf(2);
expect(response.body).to.be.sortedBy("date", { descending: true });
});
});
If I replace the before and after hooks with beforeEach and afterEach, since the tests are async, is it possible that the hook gets executed for a test and dumps out the collection before another test, which is using the collection, finishes executing? If so, what is a good way to go about populating a test database with fixtures for each test case?

Integration Test with TypeORM

I'm doing an integration test by making requests to my dev server by Supertest. But I have trouble with how to put data into the database. For example, before run GET test, I want to insert data to the database. But I even can't get a connection from TypeORM:
ConnectionNotFoundError: Connection "default" was not found.
If I even get the connection from TypeORM, how I wrap a test within transaction and rollback transaction after finish the test to make sure the integration test doesn't effect to a real database.
Basically, Is there any package that similar with factory_bot of Rails ?
describe("Templates", (): void => {
describe("GET /api/v1/templates/{templateHash}", (): void => {
let template: Template;
beforeEach(
async (): Promise<void> => {
let templateService: TemplateService = new TemplateService();
let connection: Connection = getConnection("default");
template = new Template(Buffer.from(faker.random.words()), faker.random.uuid(), faker.system.fileName());
await templateService.create(connection, template);
},
);
it("should return a template", (done): void => {
request(config.devEnvEndpoint)
.get(`api/v1/templates/${template.templateHash}`)
.set("Accept", "application/json")
.expect(200)
.end((err, response): void => {
console.log(response);
done();
});
});
});
});
It looks like typeorm is not picking up your config. I recommend using the ormconfig.json file and have two database configs - one for your development and one for testing.
By having two databases, you won't need to worry about transactions and can drop the database between tests. I suppose transactions would be faster but it really depends on your usecase (I noticed tests were much slower when making lots of connection.synchronize() calls).
Then you could use an environment variable to determine which connection to instantiate:
// db.ts
import { Connection, createConnection } from 'typeorm'
let connection: Connection | null = null
export async function getConnection() {
if (connection) {
return connection;
}
// DATABASE_CONFIG can be 'development' or 'test'
// and should correspond to the connection names
// in the ormconfig.json file
connection = await createConnection(process.env.DATABASE_CONFIG);
return connection;
}
export async function closeConnection() {
await connection?.close();
}
Then you can set the environment variable and run your tests:
// DATABASE_CONFIG=test yarn test
import { getConnection, closeConnection } from './db'
let connection: Connection;
beforeAll(async () => {
connection = await getConnection();
});
beforeEach(async () => {
// Drop everything and recreate db
await connection.synchronize(true);
});
afterAll(async () => {
await closeConnection();
});
it('should create a connection', () => {
expect(connection).toBeDefined();
})

Resources