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?
Related
I have a file that containt yield and co logic in node JS, the code look like below
module.export = {
moduleName(payload, callback){
//first logic
// second logic
co(function*(){
yield user.findById(somerandomid)
//error when find user by id
})
}
}
and this is my unit test code
beforeAll(async () => {
await dbHandler.connect();
//this part to connecto to mongodb
});
afterAll(async () => {
await dbHandler.disconnect();
});
afterEach(async () => {
jest.clearAllMocks();
});
it("test",()=>{
moduleName(somepayload)
})
so when i run my test case i got error :
MongoNotConnectedError: Client must be connected before running operations
i know this error because not connected to mongodb but i already create connection in beforeAll function, i have tried to wrap my connection in test case to yield like below
it('ts',async()=>{
co(function*(){
yield dbHandler.connect()
yield moduleName(somepayload)
}
})
but it did not work, any ideas?
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).
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?
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();
})
I have setup my project so that once a database connection is made an event is emitted. The app starts after this.
When unit testing I setup a local variable, create the database connection inside beforeEach and save the connection to the local variable.
Within this beforeEach block I can see that the database is connected. However, if I check inside a test block the local variable returns undefined. What is the correct approach for this?
- db.config.ts -
export async function connectToDatabase(mediator: EventEmitter) {
const connectionOptions = await getConnectionOptions();
const dbConnection = await createConnection(connectionOptions);
mediator.emit('db.ready', dbConnection);
}
- index.ts -
const mediator = new EventEmitter();
connectToDatabase(mediator);
mediator.on('db.ready', (db: Connection) => {
app.listen(3000);
console.log('app started');
});
Test
describe('Jest Tests', () => {
const mediator = new EventEmitter();
let dbConnection: Connection;
beforeEach(async () => {
connectToDatabase(mediator);
mediator.on('db.ready', (db: Connection) => {
dbConnection = db;
console.log(dbConnection.isConnected); // **returns TRUE**
}
}
test('Testing db connection', () => {
console.log(dbConnection.isConnected); // **dbConnection undefined**
}
}
I also tried placing the local variables outside the describe block.
beforeEach hook callback returns immediately, although it is async. You need to explicitly return a Promise from it, and fulfill it only when the db.ready event is emitted (or reject it on a certain timeout).
In pseudo code, it should look like this:
beforeEach(async () => {
return new Promise(async (fulfill, reject) => {
await connectToDatabase(mediator);
mediator.on('db.ready', db => {
dbConnection = db;
fulfill();
})
// reject on timeout
})
})
Just to add a bit more clarity to my comment, the following piece of code worked;
beforeEach(async (done) => {
connectToDatabase(mediator);
mediator.on('db.ready', (db: Connection) => {
dbConnection = db;
done();
});
});