How to test a Server Sent Events (SSE) route in NodeJS? - node.js

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.

Related

In Nock, URL seems ok to me but is not matched

I am trying to use nock to intercept a call from my app to the internet.
The goal here is to avoid using a variable external API when testing.
What I do is :
describe('My awesome test', () => {
beforeEach(() => {
let scope = nock('http://www.myexternalapi.eu')
.log(console.log)
.post('/my/awesome/path')
.query(true)
.reply(200, response);
console.error('active mocks: %j', scope.activeMocks())
});
it('Should try to call my API but return always the same stuff ', () =>{
myService.doStuffWithAHttpRequest('value', (success) => {
// The answer must always be the same !
console.log(success);
});
})
// Other tests...
}
and myService.doStuffWithAHttpRequest('value', (success) is something like that :
const body = "mybodyvalues";
const options = {
hostname: 'myexternalapi.eu',
path: '/my/awesome/path',
method: 'POST',
headers: {
'Content-Type': 'application/xml'
}
};
const request = http.request(options, (response) => {
let body = "";
response.setEncoding('utf8');
response.on('data', data => {
body += data;
});
response.on('end', () => {
parser.parseString(body, (error, result) => {
// Do a lot of cool stuff
onSuccess(aVarFromAllTheCoolStuff);
});
});
});
When runing my tests, nock display this :
active mocks: ["POST http://www.myexternalapi.eu:80/my/awesome/path/"]
Seems good ! But my request is not matched and the external API is always called !
I have tried :
beforeEach(() => {
let scope = nock('http://www.myexternalapi.eu/my/awesome/path')
.log(console.log)
.post('/')
.query(true)
.reply(200, response);
console.error('active mocks: %j', scope.activeMocks())
});
It don't work neither.
beforeEach(() => {
let scope = nock('myexternalapi.eu')
.log(console.log)
.post('/my/awesome/path')
.query(true)
.reply(200, response);
console.error('active mocks: %j', scope.activeMocks())
});
It don't work neither and display a weird URL :
active mocks: ["POST null//null:443myexternalapi.eu:80/my/awesome/path/"]
Plus something is weird :
Nock can log matches if you pass in a log function like this:
.log(console.log)
Do not display anything... ?! Any idea ?
Thanks you, I'm going crazy with this...
The values you're providing to nock and post are not quite right.
Try this.
let scope = nock('http://www.myexternalapi.eu')
.log(console.log)
.post('/my/awesome/path')
.reply(200, response);
The string argument passed to nock must be the origin, host and protocol, and must not include any path or search param/query info.
Likewise, the post method should receive the path of the call.
One helpful tool when trying to determine why Nock isn't matching a request is to debug which Nock has integrated. So however you're running your tests, prepend DEBUG=nock*.

Mocha/ Supertest is not exiting on completion of tests

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();
});

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.

How to hit axios request from Jest test page

I am trying to hit an axios request from my code.test.js file here:
import axios from 'axios'
import sinon from 'sinon';
describe('get-data', () => {
let data = {start_date:"2017-06-30",end_date:"2017-07-07",graph:"all"}
let sandbox;
let server;
beforeEach(() => {
sandbox = sinon.sandbox.create();
server = sandbox.useFakeServer();
});
afterEach(() => {
server.restore();
sandbox.restore();
});
it('should display a blankslate', (done) => {
axios.get('/api/get/data?data='+JSON.stringify(data))
.then((response.data) => {
console.log(response)
/*expect($('#users').innerHTML)
.to.equal('The list is empty.')*/ })
.then(done, done);
setTimeout(() => server.respond([200,
{ 'Content-Type': 'application/json' },
'[]']), 0);
});
})
But I get console.log(response.data) as undefined.
Can anyone tell me how to get data here in response ?
Technically in your tests you shouldn't be actually doing a request, but rather mocking it and testing side effects.
However if I remember correctly jest mocks everything by default unless told not too. In your package.json add the following section and contents:
{
"jest": {
"unmockedModulePathPatterns": [
"axios",
]
}
}
This should allow you to do your actual network request as necessary in the test. You could also pass the "automock": true, property into the jest section if you wanted to disable automocking.
Documentation:
https://facebook.github.io/jest/docs/configuration.html#automock-boolean
https://facebook.github.io/jest/docs/configuration.html#unmockedmodulepathpatterns-array-string

How should I start my nodejs application automatically for tests

I have a nodejs restful style service which has no front end, it just accepts data and then does something with it.
I have unit tested most of the method level stuff I want to, however now I want to basically do some automated tests to prove it all works together. When I am using ASP.MVC and IIS its easy as the server is always on, so I just setup the scenario (insert dummy guff into DB) then make a HttpRequest and send it to the server and assert that I get back what I expect.
However there are a few challenges in nodejs as the applications need to be run via command line or some other mechanism, so given that I have an app.js which will start listening, is there some way for me to automatically start that going before I run my tests and then close it once my tests are finished?
I am currently using Yadda with Mocha for my testing so I can keep it written in a BDD style way, however I am hoping the starting of the web app is agnostic of the frameworks I am using.
Just expose some methods to start and stop your webserver. Your app.js file could be something like this:
var app = express()
var server = null
var port = 3000
// configure your app here...
exports.start = function(cb) {
server = http.createServer(app).listen(port, function () {
console.log('Express server listening on port ' + port)
cb && cb()
})
}
exports.close = function(cb) {
if (server) server.close(cb)
}
// when app.js is launched directly
if (module.id === require.main.id) {
exports.start()
}
And then in your tests you can do something like this (mocha based example):
var app = require('../app')
before(function(done) {
app.start(done)
})
after(function(done) {
app.close(done)
})
Have a look to supertest https://github.com/visionmedia/supertest
You can write test like
describe('GET /users', function(){
it('respond with json', function(done){
request(app)
.get('/user')
.set('Accept', 'application/json')
.expect('Content-Type', /json/)
.expect(200, done);
})
})
Using gimenete's answer, here's an example of a service (server) with async await and express:
service.js:
const app = require('express')()
const config = require('./config')
let runningService
async function start() {
return new Promise((resolve, reject) => {
runningService = app.listen(config.get('port'), config.get('hostname'), () => {
console.log(`API Gateway service running at http://${config.get('hostname')}:${config.get('port')}/`)
resolve()
})
})
}
async function close() {
return new Promise((resolve, reject) => {
if (runningService) {
runningService.close(() => {
})
resolve()
}
reject()
})
}
module.exports = {
start,
close
}
service.spec.js:
const service = require('../service')
beforeEach(async () => {
await service.start()
})
afterEach(async () => {
await service.close()
})

Resources