I need to do multiple async calls in before() hook in mocha. I need to delete a user, then signup , verify email and finally login to get token to authenticate all other test cases. Here is my code snippet :
const userInfo = {
"password": "pass123",
"email": "test#email.com",
};
var token = '' , userId = '';
before((done) => {
// Delete the user if already exists
User.remove({
email: userInfo.email
}).then((res) => {
// console.log(res.result);
})
.end(done);
done();
});
before((done) => {
request(app)
.post('/api/users')
.send(userInfo)
.expect(200)
.expect((res) => {
})
.end(done);
});
before((done) => {
User.findOne({
email: userInfo.email
}).then((res) => {
userId = res._id;
request(app)
.post('/api/users/verify-email')
.send({'token' : userId})
.expect(200)
.expect((res) => {
})
.end(done);
})
.end(done);
done();
});
Here these calls are not executing in sequence. I need to fetch userId before verifying the email, but I am getting below error:
POST /api/users/verify-email 401 4.082 ms - 45
1) "before all" hook
First of all, yes, mocha allows multiple before hooks and guarantees that they are called in the right order. To make sure of this you could run this snippet.
'use strict';
const results = [];
const assert = require('assert');
before(done => {
setTimeout(() => {
console.log(`First 'before'`);
results.push(1);
done(); //Will be called last
}, 1000)
});
before(done => {
setTimeout(() => {
console.log(`Second 'before'`);
results.push(2); //Will be called second
done();
}, 300)
});
before(done => {
setTimeout(() => {
console.log(`Third 'before'`);
results.push(3); //Will be called first
done();
}, 100)
});
describe('Before hooks order', () => {
it('should before hooks sequentially', () => {
//Check if the hooks were called in the right order anyway
assert.deepEqual(results, [1, 2, 3]);
});
});
//Output is:
// First 'before'
// Second 'before'
// Third 'before'
But to make this happen you need to call done() only when all the async operations are done to let mocha know that the hook is completed and it should run the next one.
Also there is a rule that any Node.js callback must be called only once. So here are a couple of fixes:
before((done) => {
// Delete the user if already exists
User
.remove({
email: userInfo.email
})
.then((res) => {
// console.log(res.result);
})
.end(done);
//Do not call done here() because User.remove have only started
//We need to wait until it finishes. Done will be called in .end method
// done();
});
before((done) => {
//All good here
request(app)
.post('/api/users')
.send(userInfo)
.expect(200)
.expect((res) => {
})
.end(done);
});
before((done) => {
User.findOne({
email: userInfo.email
}).then((res) => {
userId = res._id;
//You need a return statement here so the outer promise waits
//Until the internal call finishes
return request(app)
.post('/api/users/verify-email')
.send({'token': userId})
.expect(200)
.expect((res) => {
});
//We must not call callback multiple times, so remove this.
// .end(done);
})
//Now this 'end' will happen only after both calls finish
.end(done);
//Remove this please, for the same reason as with the first 'before'
// done();
});
Please check it out. I'm not able to run your code (don't have your api), so please let me know of any problems.
Related
I try to test a middleware in mocha.
The problem is, that all "it" calls waiting for the previous one to finish, before executing their callback.
it(`should trigger pre hook 1`, (done) => {
use((next) => {
setTimeout(() => {
done();
next();
}, 1000);
});
});
it(`should trigger pre hook 2`, (done) => {
use((next) => {
setTimeout(() => {
done();
next();
}, 1000);
});
});
start(() => {
// middleware done
});
The second it(...) waits for the first to complete.
And thats exactly the problem, since the second use(...) is not called before i fire the start(...) function, so it never gets executed and the test fails.
How can i tell mocha to execute all "it" callbacks and wait not for the previous one to complete (or fails)?
Try a spy instead of done so you can layout the tests as needed without relying on mocha to control the flow.
describe('middleware', function(){
// initialise use/start for this test
const m1 = sinon.spy()
const m2 = sinon.spy()
use((next) => {
setTimeout(() => {
m1();
next();
}, 1000);
});
use((next) => {
setTimeout(() => {
m2();
next();
}, 1000);
});
it(`should process a request through middleware`, function(done){
start(() => {
// middleware done
expect(m1.calledOnce, `m1`).to.equal(true)
expect(m2.calledOnce, `m2`).to.equal(true)
done()
});
})
})
The spy will also let you check on more complex call scenarios in the middleware when you have functional code in there.
When I use done in beforeEach or afterEach I get this error “Error: Resolution method is overspecified” and the test fails.
But now if remove the done() all my tests pass but the terminal hangs without exiting the test script.
I am using knex.js as a query builder.
Is there a solution to this problem?
beforeEach(async (done) => {
await db.migrate.rollback(migrationConfig);
await db.migrate.latest(migrationConfig);
await db.seed.run(seedConfig);
done();
});
// cleaning db before running tests
afterEach(async (done) => {
await db.migrate.rollback(migrationConfig);
done();
});
describe("POST /user/login", () => {
it("should return a jwt after loging in user", (done) => {
chai
.request(server)
.post("/user/login")
.send({
email: "saaransh#test.com",
password: "test123",
})
.end((err, res) => {
res.should.have.status(200);
res.should.be.json;
res.body.should.have.property("token");
done();
});
});
});
If you are using async handler functions, then you should not give it parameter done. Promise returned by async function will tell Mocha when executing the function is ready.
beforeEach(async () => {
await db.migrate.rollback(migrationConfig);
await db.migrate.latest(migrationConfig);
await db.seed.run(seedConfig);
});
// cleaning db before running tests
afterEach(async () => {
await db.migrate.rollback(migrationConfig);
});
I have some code in an Express route which talks to AWS Cognito and am having trouble working out how to mock it in tests.
cognitoExpress.validate(accessTokenFromClient, (err, response) => {
if (err) return res.status(401).json({ error: err });
res.json({ data: `Hello ${response.username}!` });
});
Then in my test I want to say cognitoExpress.validate should be called once and return {username: 'test user'} so that it doesnt hit the network and doesnt actually call AWS Cognito
it('It should returns 200 with a valid token', async done => {
const { cognitoExpress } = require('../helpers/cognitoExpress');
// I have tried
jest.mock('../helpers/cognitoExpress');
// and this
jest.mock('../helpers/cognitoExpress', () => ({
validate: jest.fn()
}));
const token = 'sfsfdsfsdfsd';
const response = await request.get('/').set('Authorization', token);
expect(cognitoExpress.validate).toHaveBeenCalledWith(token);
expect(response.body).toEqual({ data: 'Hello test user' });
done();
});
Thanks in advance....
let spyInstance = undefined;
beforeAll(() => {
spyInstance = jest.spyOn(cognitoExpress.prototype, "validate").mockImplementation(() => {
// Replace the body of 'validate' here, ensure it sets
// response body to {username: 'test user'} without calling AWS
...
});
});
afterAll(() => {
expect(spyInstance).toBeDefined();
expect(spyInstance).toHaveBeenCalledTimes(1);
jest.restoreAllMocks();
});
it("It should call mocked cognitoExpress.validate once", async done => {
...
});
A similar and working test in my project. Instead of cognitoExpress.validate it mocks and tests SampleModel.getData
Create file ../helpers/__mocks__/cognitoExpress.js with mocked function you want to use. It is essential to call the folder __mocks__. You can modify a functions and return any data you want.
example
module.exports = {
validate: () => { username: 'test user' }
}
Now you can use jest.mock('../helpers/cognitoExpress'), but I recommend you to place it to some global or test setup file, not to separate tests.
Jest Manual Mocks
I need to test if my POST request to my endpoint works properly with a Jest test. I had the idea of first getting the count of my Services table (I'm using sequelize orm), then to send a new post request and to finally get the new count and compare if the old count + 1 will equal to the new count, if true then the POST request works just fine.
test('Create a valid Service', async (done) => {
const service = {
name: "cool",
description: "description"
};
await Service.count().then(async function (count) {
await request(app)
.post('/api/services')
.send(service)
.then(async () => {
await Service.count().then(function (newcount) {
expect(newcount).toBe(count + 1);
});
})
.catch(err => console.log(`Error ${err}`));
});
});
For me the test looks fine, but when I run it I get:
Timeout - Async callback was not invoked within the 5000ms timeout specified by jest.setTimeout.
Is something missing or is there even a better way to test a POST request? with Jest?
It is because you are not calling the done callback passed in jest callback function. It can be done like this.
test('Create a valid Service', async(done) => {
const service = {
name: "cool",
description: "description"
};
await Service.count().then(async function (count) {
await request(app)
.post('/api/services')
.send(service)
.then(async() => {
await Service.count().then(function (newcount) {
expect(newcount).toBe(count + 1);
// execute done callback here
done();
});
})
.catch(err => {
// write test for failure here
console.log(`Error ${err}`)
done()
});
});
});
You can also write this code in this way so that the readability can be improved and maximize the use of async/await.
test('Create a valid Service', async(done) => {
const service = {
name: "cool",
description: "description"
};
try {
const count = await Service.count();
await request(app).post('/api/services').send(service)
const newCount = await Service.count()
expect(newCount).toBe(count + 1);
done()
} catch (err) {
// write test for failure here
console.log(`Error ${err}`)
done()
}
});
By default Jest also resolves the promise in async/await case. We can achieve this without the callback function also
test('Create a valid Service', async() => {
const service = {
name: "cool",
description: "description"
};
try {
const count = await Service.count();
await request(app).post('/api/services').send(service)
const newCount = await Service.count()
expect(newCount).toBe(count + 1);
} catch (err) {
// write test for failure here
console.log(`Error ${err}`)
}
});
I wrote the testing code and it worked fine but after I added other routes that didn't relate to the current code, the code became corrupted, especially at this point:
1) "before each" hook for "should create new todo":
Error: timeout of 2000ms exceeded. Ensure the done() callback is being called in this test`.
const todos = [{
_id: new ObjectID(),
text: 'first test todo',
completed: false,
completedAt: null
}, {
_id: new ObjectID(),
text: 'second test todo',
completed: true,
completedAt: 333
}];
beforeEach((done) => {
Todo.remove({}).then(() => {
return Todo.insertMany(todos);
}).then((docs) => {
done();
});
})
describe('post /todo', () => {
it('should create new todo', (done) => {
let text = 'here from supertest';
request(app)
.post('/todos')
.send({
text
})
.expect(200)
.expect((res) => {
expect(res.body.text).toBe(text);
})
.end((err, res) => {
if (err) {
return done(err);
}
Todo.find({
text
}).then((res) => {
expect(res.length).toBe(1);
expect(res[0].text).toBe(text);
done();
})
.catch((e) => {
done(e);
});
});
});
});
The whole project is in this github libary called TestMongo, where you can check the last two commits that have the issues with testing that I face but when you try it by postman it works fine. When one reverts to the third commit from the end, the testing works correctly.
Probably we need to increase the timeout of test to bigger number
beforeEach(function(done) { // dont use arrow function to use this.timeout
this.timeout(5000); // override default 2000 ms
Todo.remove({}).then(() => {
return Todo.insertMany(todos);
}).then((docs) => {
done();
});
})
Hope it helps