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.
Related
I have a Server Sent Events route on my NodeJS app that clients can subscribe to for getting real-time updates from the server. It looks like follows:
router.get('/updates', (req, res) => {
res.writeHead(200, {
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache',
'Connection': 'keep-alive'
})
const triggered = (info) => {
res.write(`\ndata: ${JSON.stringify(info)}\n\n`)
}
eventEmitter.addListener(constants.events.TRIGGERED, triggered)
req.on('close', () => {
eventEmitter.removeListener(constants.events.TRIGGERED, triggered)
})
})
Testing a traditional route using supertest is simple enough in node:
test('Should get and render view', async() => {
const res = await request(app)
.get('/')
.expect(200)
expect(res.text).not.toBeUndefined()
})
However, this does not work when testing a SSE route.
Does anyone have any ideas on how to test a SSE route with Node? It doesn't necessarily have to be tested with supertest. Just looking for ideas on how to test it, supertest or otherwise.
EDIT:
I have an idea about how to integration test this. Basically, one would have to spin up a server before the test, subscribe to it during the test and close it after the test. However, it doesn't work as expected in Jest when I use beforeEach() and afterEach() to spin up a server.
I would mock/fake everything used by the endpoint, and check if the endpoint executes in the right order with the correct variables. First, I would declare trigger function and close event callback outside of the endpoint so that I could test them directly. Second, I would eliminate all global references in all functions in favor of function parameters:
let triggered = (res) => (info) => {
res.write(`\ndata: ${JSON.stringify(info)}\n\n`);
}
let onCloseHandler = (eventEmitter, constants, triggered, res) => () => {
eventEmitter.removeListener(constants.events.TRIGGERED, triggered(res));
}
let updatesHandler = (eventEmitter, constants, triggered) => (req, res) => {
res.writeHead(200, {
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache',
'Connection': 'keep-alive'
});
eventEmitter.addListener(constants.events.TRIGGERED, triggered(res));
req.on('close', onCloseHandler(eventEmitter, constants, triggered, res));
};
router.get('/updates', updatesHandler(eventEmitter, constants, triggered));
With this code, the test cases would be like:
test("triggered", () => {
let res;
beforeEach(() => {
res = generateFakeRespone();
});
it("should execute res.write with the correct variable", () => {
trigger(res)("whatever");
expect(res.write).to.have.been.called.once;
expect(res.write).to.have.been.called.with(`\ndata: ${JSON.stringify("whatever")}\n\n`);
});
});
test("onCloseHandler", () => {
let res;
let eventEmitter;
let constants;
let triggered;
beforeEach(() => {
res = Math.random();
eventEmitter = generateFakeEventEmitter();
constants = generateFakeConstants();
triggered = generateFakeTriggered();
});
it("should execute eventEmitter.removeListener", () => {
onCloseHandler(eventEmitter, constants, triggered, res);
expect(eventEmitter.removeListener).to.have.been.called.once;
expect(eventEmitter.removeListener).to.have.been.called.with(/*...*/)
});
});
test("updatesHandler", () => {
beforeEach(() => {
req = generateFakeRequest();
res = generateFakeRespone();
eventEmitter = generateFakeEventEmitter();
constants = generateFakeConstants();
triggered = generateFakeTriggered();
});
it("should execute res.writeHead", () => {
updatesHandler(eventEmitter, constants, triggered)(req, res);
expect(res.writeHead).to.have.been.called.once;
expect(res.writeHead).to.have.been.called.with(/*...*/)
});
it("should execute req.on", () => {
//...
});
// more tests ...
});
With this style of coding and testing, you have the ability to make very detailed unit test. The downside is that it take much more effort to test everything properly.
Have a look at the tests for the express-sse library. They spin up the server on a port, then create an instance of EventSource and connect it to the SSE end-point on that running server.
Something like this:
describe("GET /my-events", () => {
let events
let server
beforeEach(function (done) {
events = new EventEmitter()
const app = createMyApp(events)
server = app.listen(3000, done)
})
afterEach(function (done) {
server.close(done)
})
it('should send events', done => {
const es = new EventSource('http://localhost:3000/my-events')
events.emit('test', 'test message')
es.onmessage = e => {
assertThat(e.data, equalTo('test message'))
es.close()
done()
}
})
})
That seems like the right way to test it, to me.
I am using the mocha testing framework, and after running the following test it does not exit.
I have tried Promises and async await with no luck. --exit at the end of the mocha command works, but I want to find the source of the issue.
I am wondering if it is the knex database connection when running beforeEach and afterEach functions. However, I do not know how to disconnect the db connection other than destroy(), and if this is used the following tests do not run.
Can anybody see anything within the code that could be causing this issue? Or recommend another way to remedy this?
const app = require('../../app');
const request = require('supertest');
describe('Route: /' + route, () => {
let token = '';
let route = 'user';
before(function (done) {
const user = {email: 'admin#email.com', password: 'password'};
request(app)
.post('/login')
.send(user)
.end((err, res) => {
token = res.body.token;
done();
});
});
beforeEach(async () => {
await knex.migrate.rollback();
await knex.migrate.latest();
await knex.seed.run();
});
afterEach(() => knex.migrate.rollback());
it(`should not be able to consume /${route} since no token was sent`, (done) => {
request(app)
.get(`/${route}`)
.expect(401, done)
});
it(`should be able to consume /${route} since a valid token was sent`, (done) => {
request(app)
.get(`/${route}`)
.set('Authorization', 'Bearer ' + token)
.expect(200, done);
});
});
For anyone who comes across this and has a similar problem.
Using the following links;
- GitHub mocha debug example
- Mocha docs -exit
- wtfnode
I was able to debug the problem.
wtfnode used within my test showed me that my database was still connected with the console reading.
const wtf = require('wtfnode');
after(wtf.dump()); // place within test describe body
Returned;
- Sockets:
- 127.0.0.1:58898 -> 127.0.0.1:5432
- Listeners:
- connect: Connection.connect # <user_path>/node_modules/pg/lib/connection.js:59
I am using knex to connect to the database, so I've added code below to the file helper.js in my test directory.
/test/helper.js
const knex = require('../database/db');
before(function () {
if (!knex.client.pool) return knex.initialize();
});
beforeEach(async function () {
await knex.migrate.rollback();
await knex.migrate.latest();
await knex.seed.run();
});
afterEach(function () {
return knex.migrate.rollback()
});
after(function () {
return knex.destroy();
});
I created an app in node.js which gives me a WebSocket interface using the 'ws' package from NPM on the server. Now I want to test this interface with Jest. The test runs successful but Jest does not exit and gives me the error:
Jest did not exit one second after the test run has completed.
I read the documentation and found that I have to use the done parameter in the test and call it when the test has finished.
The server will be started in the beforeAll function and stopped in the afterAll function given by Jest.
describe('app', () => {
it('connect websockets response' (done), => {
expect.assertions(1);
new WebSocket(`ws://localhost:${port}`).on('message' (msg), => {
expect(JSON.parse(msg).id).toEqual(0);
done();
})
});
});
I expect that Jest stops successful after the test has finished.
I have learned that I have to close the WebSocket connection in the test itself, and wait for the closing event.
describe('app', () => {
it('connect websockets response', (done) => {
expect.assertions(1);
const ws = new WebSocket(`ws://localhost:${port}`)
.on('message', (msg) => {
expect(JSON.parse(msg).id).toEqual(0);
ws.close();
})
.on('close', () => done());
});
});
This is what worked for me using TypeScript
test('connect websockets response', (done) => {
const ws = new WebSocket(`ws://localhost:${port}`);
const listener = (msg: MessageEvent) => {
console.log(JSON.parse(msg.data));
// Put you expect statement here
ws.removeEventListener('message', listener);
ws.close();
};
ws.addEventListener('message', listener);
ws.addEventListener('close', () => done());
}, 30000);
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
I have a web app with a spec like this:
describe('Hook them up', () => {
var server;
beforeEach(done => {
server = app.listen(done);
});
before(done => {
// Does this run before or after "beforeEach"
// If I try to access the api at this point I get an ECONNREFUSED
});
after(done => {
server.close(done);
});
it('should set the \'createdAt\' property for \'DndUsers\' objects', done => {
api.post('/api/tweets')
.send({ text: 'Hello World' })
.then(done)
.catch(err => {
console.log(err);
done();
});
});
});
In some other project of mine, if I try to access the api in the before block it works fine, as if the beforeEach was already run.
See my answer here to a very similar question.
Mocha's test runner explains this functionality the best in the Hooks section of the Mocha Test Runner.
From the Hooks section:
describe('hooks', function() {
before(function() {
// runs before all tests in this block
});
after(function() {
// runs after all tests in this block
});
beforeEach(function() {
// runs before each test in this block
});
afterEach(function() {
// runs after each test in this block
});
// test cases
it(...); // Test 1
it(...); // Test 2
});
You can nest these routines within other describe blocks which can also have before/beforeEach routines.
This should give you
hooks
before
beforeEach
Test 1
afterEach
beforeEach
Test 2
afterEach
after