How to test a asynchronous server code using mocha chai? - node.js

In my mocha-test suite, I want to test a functionality which makes a asynchronous call behind the scene. How can I wait until the asynchronous call has finished ?
For example, I make two back to back post calls. The first post call also makes an asynchronous call internally, until that asynchronous operation is complete the second post call won't pass.
I need either of below:
1) to put a delay between the two post calls so that to make sure the asynchronous part in the first post is complete.
2) to make the second post call repetitively until it passes.
3) or how to test out the asynchronous call through mocha-chai ?
Below is the example:
describe('Back to back post calls with asynchronous operation', ()=> {
it('1st and 2nd post', (done) => {
chai.request(server)
.post('/thisis/1st_post')
.send()
.end((err, res) => {
expect(res.statusCode).to.equal(200);
/* HERE I Need A Delay or a way to call
the below post call may be for 5 times */
chai.request(server)
.post('/thisis/second_post')
.send()
.end((err, res) => {
expect(res.statusCode).to.equal(200);
});
done();
});
});
});
Is there a way to handle this ? Please help.
Thanks.

In order to test an asynchronous function with mocha you have the following possibilities
use done only after the last callback in your sequence was executed
it('1st and 2nd post', (done) => {
chai.request(server)
.post('/thisis/1st_post')
.send()
.end((err, res) => {
expect(res.statusCode).to.equal(200);
/* HERE I Need A Delay or a way to call
the below post call may be for 5 times */
chai.request(server)
.post('/thisis/second_post')
.send()
.end((err, res) => {
expect(res.statusCode).to.equal(200);
//call done only after the last callback was executed
done();
});
});
});
use done callback with promises
describe('test', () => {
it('should do something async', (done) => {
firstAsyncCall
.then(() => {
secondAsyncCall()
.then(() => {
done() // call done when you finished your calls
}
})
});
})
After a proper refactor you will get something like
describe('test', () => {
it('should do something async', (done) => {
firstAsyncCall()
.then(secondAsyncCall())
.then(() => {
// do your assertions
done()
})
.catch(done)
})
})
use async await, much cleaner
describe('test', () => {
it('should do something async', async () => {
const first = await firstAsyncCall()
const second = await secondAsyncCall()
// do your assertion, no done needed
});
})
An other moment to keep in mind is the --timeout argument when running mocha tests. By default mocha is waiting 2000 miliseconds, you should specify a larger amount when the server is responding slower.
mocha --timeout 10000

Related

what is the difference between .done() and .end() function in node and when they shall be used?

I am trying to understand the use case of end() function while writing the API test script in mocha and Chai. Also at the same time, I am confused about whether I shall use the done() function here or not and also what is the exact difference between .end() and .done().
Here's the code:
describe("Suite", () => {
it('Post Test case', (done) => {
request('https://reqres.in')
.post('/api/users')
.send({
"name": "morpheus",
"job": "leader"
})
.set('Accept', 'application/json')
.expect(200,'Content-Type', /json/)
.then((err,res) => {
console.log(JSON.stringify(err))
console.log(JSON.stringify(res.body))
console.log(JSON.stringify(" "))
})
done();
});
it('Put Test case', (done) => {
request('https://reqres.in')
.put('/api/users/2')
.send({
"name": "morpheus",
"job": "zion residents"
})
.expect(200)
.end((err, res) => {
console.log(JSON.stringify(err))
console.log(JSON.stringify(res.body))
console.log(JSON.stringify(" "))
})
done();
})
})
You are mixings things a little bit.
end is a method of the expressjs framework and it ends the server response.
done is a parameter of mocha test function. You call this parameter when you are finished with your asynchronous test to let the mocha know that your asynchronous code is done executing, and it can move on to another test.
And in your case, you need them both.

Running multiple mocha files and suites with same context

I'm trying to run several integration tests with some shared context. The context being shared is a single express application, and I'm trying to share it across suites / files because it takes a few seconds to spin up.
I got it to work by instantiating a "runner" mocha test suite, that would have test functions that would just require each test file as needed, and this was working well (a side effect is that the test requiring the child test file would finish as "success" before any of the tests inside the file would actually run, but this was a minor issue)
// test-runner.js:
describe('Integration tests', function () {
let app
let log
this.timeout(300000) // 5 mins
before(function (done) {
app = require('../app')
app.initialize()
.then(() => {
done()
})
.catch(err => {
log.error(err)
done(err)
})
})
it('Running api tests...', (done) => {
require('./integration/api.test')(app)
done()
})
// ./integration/api.test.js:
module.exports = (app) => {
let api = supertest.agent(app)
.set('Content-Type', 'application/json')
describe('Authorization', () => {
describe('Trying to access authorization sections', () => {
it('should be denied for /home', async () => {
await api.get(`${baseUrl}/home`)
.expect(STATUS_CODE.UNAUTHORIZED)
})
...
The Problem:
I want to signal the test runner that all of the tests in the imported suite have finished, so I can call shutdown logic in the test runner and end the test cleanly. In standard test functions, you can pass a done function to signal that the code in the test is complete, so I wrapped each of the child tests in a describe block to use the after hook to signal that the whole test module was done:
// test-runner.js:
describe('Integration tests', function () {
let app
let log
this.timeout(300000) // 5 mins
before(function (done) {
app = require('../app')
app.initialize()
.then(() => {
done()
})
.catch(err => {
log.error(err)
done(err)
})
})
it('Running api tests...', (done) => {
require('./integration/api.test')(app, done)
})
// ./integration/api.test.js:
module.exports = (app, done) => {
let api = supertest.agent(app)
.set('Content-Type', 'application/json')
describe('All api tests', () => {
let api
before(() => {
api = supertest.agent(app)
.set('Content-Type', 'application/json')
})
after(() => {
done() // should be calling the done function passed in by test runner
})
describe('Authorization', () => {
describe('Trying to access authorization sections', () => {
it('should be denied for /home', async () => {
await api.get(`${baseUrl}/home`)
.expect(STATUS_CODE.UNAUTHORIZED)
})
...
but when I do this, the test suites just don't run. The default timeout will just expire, and if I set a higher timeout, it just sits there (waiting for the longer timeout). If I hook into a debug session, then the test exits immediately, and the after hook (and before!) never get called.
I'm open to other ideas on how to do this as well, but I haven't found any good solutions that that allow sharing some context between tests, while having them broken into different files.

Using await / async with mocha, chai

I'm quite new to node and express.
And have been trying to write test code using mocha, chai and chai-http.
Here's the part of source code.
const mongoose = require('mongoose'),
User = require('../../models/user');
const mongoUrl = 'mongodb://xxxxxxxxxxx';
describe('/test', function() {
before('connect', function() {
return mongoose.createConnection(mongoUrl);
});
beforeEach(async function(done) {
try {
await User.remove({}); // <-- This doesn't work
chai.request('http://localhost:3000')
.post('/api/test')
.send(something)
.end((err, res) => {
if (err) return done(err);
done();
});
} catch (error) {
done(error);
}
});
});
And I get the following error with "npm test"(nyc mocha --timeout 10000 test/**/*.js).
Error: Timeout of 10000ms exceeded. For async tests and hooks, ensure "done()" is called; if returning a Promise, ensure it resolves.
I confirmed the database connection works properly from log.
And seems I get the timeout error with await User.remove({}).
I've also tried different methods such as a User.save()
But, I got the same error.
Do I need to do something special with database model and connection?
This is all pretty simple.
To avoid the error you must not use both done and async/await in Mocha at the same time. Either use async/await and remove both done as function parameter and done() call. Or use done. Then remove both async/await. See the example tests below for each case.
Use try/catch with async/await as you would normally use it with synchronous code.
Following are the most basic Mocha tests with both async/await and done approaches testing the same basic HTTP server endpoint.
This is async/await approach.
it('with async/await', async function() {
const res = await chai.request(server)
.get('/')
.send();
assert.equal(res.status, 200);
});
This is done approach.
it('with done & callbacks', (done) => {
chai.request(server)
.get('/')
.end((err, res) => {
assert.equal(res.status, 200);
done();
});
});
See the full test file snippet.
For working example additionally spin basic Express server as the tests counterpart in src/app.js.
See Chai HTTP plugin docs for more info on what you can do with request testing.
This is it.
I had the same problem and have not found a way to get any promises that involve mongoose working with Mocha/Chai.
What may help you is doing what I did and putting your mongoose code in a script so you can run it with node <scriptfile>.js. You can use that to confirm it's working properly by itself. In my test, the mongoose operation finished in less than a second. You can also call that file from another (non-test related) to confirm it executes properly and returns a promise. You can see from my example how to make sure you close db properly. Partial example:
...
db.close();
return new Promise((resolve) => {
db.on('disconnected', () => {
console.log('***************************************Mongoose CONNECTION TERMINATED');
resolve('user ready');
});
});
...
You may also find some clues by looking at the following issues here and here.
The work around that I did after wasting too much time trying to figure out this crazy behavior was to perform my mongoose needs in a route. I wrap each request that needs to use it in the end block of the extra chai.request... or use async. Example:
describe('something', () => {
it('should do something and change it back', async () => {
try {
// change user password
let re1 = await chai.request(app)
.post('/users/edit')
.set('authorization', `Bearer ${token}`)
.send({
username: 'user#domain.com',
password: 'password6',
});
expect(re1.statusCode).to.equal(200);
// change password back since before hook not working
let re2 = await chai.request(app)
.post('/users/edit')
.set('authorization', `Bearer ${token}`)
.send({
username: 'user#domain.com',
password: 'password6',
passwordNew: 'password',
passwordConfirm: 'password',
});
expect(re2.statusCode).to.equal(200);
} catch (error) {
// error stuff here
}
});
Note that using the try/catch syntax above will cause test that should normally fail to show passing and the results will be caught in the catch block. If you want to avoid that, just remove the try/catch.
How did you implement ./models/user? await only works if User.remove() returns a promise, not if it expects a callback. I would add debug information to your User.remove() function to see where it gets stuck.

Mocha unit test for Promise throws error

I'm trying to unit test a promise. Here is the code :
it('it should return some 10 user data with ok status code when called with url ', (done) => {
return user.getUsers('https://jsonplaceholder.typicode.com/users')
.then((response) => {
console.log('me here')
assert.equal(JSON.parse(response).length, 10)
})
.catch((err)=>{
console.log('me in error')
assert.fail('err')
})
})
The above code when run throws the following error :
Error: Timeout of 2000ms exceeded. For async tests and hooks, ensure "done()" is called; if returning a Promise, ensure it resolves. (C:\Users\ajay\jay-workspace\UniTestModule\test\user.test.js)
done isn't called, this results in test timeout.
Mocha natively supports promises. done shouldn't be used when there are promises; instead, promises should be returned:
it('it should return some 10 user data with ok status code when called with url ', () => {
return user.getUsers('https://jsonplaceholder.typicode.com/users')
.then((response) => {
console.log('me here')
assert.equal(JSON.parse(response).length, 10)
});
})
Rejected promise will fail the test, no need for assert.fail as well.
You should call done() callback when using async testing (for details check https://mochajs.org/#asynchronous-code).
it('it should return some 10 user data with ok status code when called with url ', (done) => {
user.getUsers('https://jsonplaceholder.typicode.com/users')
.then((response) => {
console.log('me here')
assert.equal(JSON.parse(response).length, 10);
done();
})
.catch((err)=>{
console.log('me in error')
assert.fail('err');
done(err);
})
You should use --timeout hook and increase the number of ms like ./node_module/.bin/mocha test/ --timeout=5000
OR you can add this.timeout(5000) in the test case body
it('it should return some 10 user data with ok status code when called with url ', (done) => {
user.getUsers('https://jsonplaceholder.typicode.com/users')
.then((response) => {
console.log('me here')
assert.equal(JSON.parse(response).length, 10);
this.timeout(5000);
setTimeout(done, 3000);
})
https://mochajs.org/#test-level

Nock not working for multiple tests running together

I am using nock library to stub my http calls.
Different test files require('nock') and do their stubbing.
If each test is run separately, all is passing.
But if all tests run together, later tests fail because instead of nock, actual request was made.
Consider below code snippet for example. It has two different describe blocks, each with multiple test cases. If I run this file node node_modules/mocha/bin/_mocha test.js then the first two tests will pass, but the third test (in different describe block) would fail because it would actually call the google URL.
/* eslint-env mocha */
let expect = require('chai').expect
let nock = require('nock')
let request = require('request')
let url = 'http://localhost:7295'
describe('Test A', function () {
after(function () {
nock.restore()
nock.cleanAll()
})
it('test 1', function (done) {
nock(url)
.post('/path1')
.reply(200, 'input_stream1')
request.post(url + '/path1', function (error, response, body) {
expect(body).to.equal('input_stream1')
done()
})
})
it('test 2', function (done) {
nock(url)
.post('/path2')
.reply(200, 'input_stream2')
request.post(url + '/path2', function (error, response, body) {
expect(body).to.equal('input_stream2')
done()
})
})
})
// TESTS IN THIS BLOCK WOULD FAIL!!!
describe('Test B', function () {
after(function () {
nock.restore()
nock.cleanAll()
})
it('test 3', function (done) {
nock('http://google.com')
.post('/path3')
.reply(200, 'input_stream3')
request.post('http://google.com' + '/path3', function (error, response, body) {
expect(body).to.equal('input_stream3')
done()
})
})
})
Funny thing is, if I do console.log(nock.activeMocks()), then I can see that nock did register the URL to mock.
[ 'POST http://google.com:80/path3' ]
As discussed in this Github Issue, nock.restore() removes the http interceptor itself. When you run nock.isActive() after calling nock.restore() it will return false. So you need to run nock.activate() before using it again.
Solution 1:
Remove nock.restore().
Solution 2:
Have this before() method in your test.
before(function (done) {
if (!nock.isActive()) nock.activate()
done()
})

Resources