I am using express.js version 2.5.8 (from legacy code) and am looking to test route loading using supertest. I am having an issue with the server running, but not stopping. I run my tests using jasmine-node, which indicates that my assertion succeeds. However, the console shows that the process is still running.
var request = require('supertest')
, express = require('express');
describe('Example Test', function() {
var app;
beforeEach(function() {
app = express.createServer();
app.configure(function() {
app.set('port', process.env.PORT || 3000);
app.use(express.logger('dev'));
app.use(express.bodyParser());
app.use(express.methodOverride());
});
app.get('/', function(req, res){
res.send(200);
});
});
it('index should return 200', function(done) {
request(app)
.get('/')
.expect(200)
.end(function(err, res) {
expect(err).toBe(null);
done();
});
});
});
This example is adapted from one likely using express.js 3.x.x. My assumption is that the express server behaves differently, leading to it not closing when the request terminates inside the test. I am uncertain how to correct this issue.
The server is still running because you aren't closing it anywhere. Just add app.close() to your end(function(){}) to stop the express server. If you also want to exit from node use process.exit().
Related
In order to automate testing of my Express.js, I want to make sure that the tests have finished running when each test decides the results.
Consider this simplified model of that situation:
main.js
#!/usr/bin/env node
const express = require('express')
const app = express()
app.get('/', (req, res) => {
res.send('hello world')
})
const server = app.listen(3000, function () {
// I would run tests here, which is the 'listen' event.
console.log('Server started')
// After the tests, I will close the server from here.
this.close()
})
// I want to make sure that this runs only after the asserts were done,
// otherwise the test would miss possible failures.
console.log('After listen')
package.json
{
"name": "tmp",
"version": "1.0.0",
"dependencies": {
"express": "4.17.1"
}
}
Running main produces:
After listen
Server started
because the server must be asynchronously starting to listen to connections, so After listen runs first. But I want instead to have:
Server started
After listen
I know about the 'close' event which would allow me to write:
#!/usr/bin/env node
const express = require('express')
const app = express()
app.get('/', (req, res) => {
res.send('hello world')
})
const server = app.listen(3000, function () {
// I would run tests here, which is the 'listen' event.
console.log('Server started')
// After the tests, I will close the server from here.
this.close()
})
server.on('close', () => {
console.log('After listen')
})
but then this just shifts the problem further out to the test infrastructure, which has to make sure that it is able to wait for the After listen to happen. Yes, in Mocha this is possible with done(), but it would be better to have something simpler more test-system agnostic.
Tested on Node.js 14.15.0.
Related:
Express.js - Listen for shutdown
What's the best way to test express.js API
Wait for the server to listen before running tests
OK, I've studied promises a bit more as I should have done earlier, and this is satisfactory approach:
#!/usr/bin/env node
(async () => {
const express = require('express')
const app = express()
app.get('/', (req, res) => {
res.send('hello world')
})
await new Promise((resolve, reject) => {
const server = app.listen(3000, function () {
// I would run tests here, which is the 'listen' event.
console.log('Server started')
// After the tests, I will close the server from here.
this.close()
resolve()
})
})
console.log('After listen')
})()
This is basically the same general pattern that you can use to synchronize any other asynchronous callback function in JavaScript, e.g. for HTTP requests: Synchronous request in Node.js
Here I illustrate a fuller test setup which also makes the HTTP requests and asserts things: What's the best way to test express.js API
For Botkit v0.7.4, a custom express server can be used as follows
module.exports = function(webserver, controller) {
webserver.post('/slack/receive', function(req, res) {
res.status(200);
res.send('ok');
controller.handleWebhookPayload(req, res);
});
return webserver;
}
is something similar available for the latest version also.
Got the above sample from here
So far, I've got this
const adapter: SlackAdapter = new SlackAdapter({
getTokenForTeam,
getBotUserByTeam,
clientId: config.get('slack.clientId'),
clientSecret: config.get('slack.clientSecret'),
clientSigningSecret: config.get('slack.signingSecret'),
scopes: ['bot', 'team:read', 'users:read', 'users:read.email', 'chat:write:bot'],
redirectUri: config.get('slack.redirectUri')
});
adapter.use(new SlackEventMiddleware());
adapter.use(new SlackMessageTypeMiddleware());
// webserver is an express app - like so - const webserver = express();
const controller = new Botkit({
adapter,
webserver,
webhook_uri: '/slack/receive'
});
controller.ready(() => controller.loadModules('./features'));
How to setup the /slack/receive route so that the challenge verification when activating the events API and all further events emitted from Slack will be handled properly
I can test my models fine with AVA, but I would like to also test the routes.
I feel it should be possible to access the Express app object, and pass it a URL, then see what comes back, but I do not know how to get the Express object to use it.
After some playing around and referencing the supertest repo, I was able to get the following working:
const test = require("ava");
const request = require("supertest");
const express = require("express");
test("test express handler with supertest", async t => {
// in async tests, it's useful to declare the number of
// assertions this test contains
t.plan(3);
// define (or import) your express app here
const app = express();
app.get("/", (req, res) => {
res.json({
message: "Hello, World!",
});
});
// make a request with supertest
const res = await request(app).get("/").send();
// make assertions on the response
t.is(res.ok, true);
t.is(res.type, "application/json");
t.like(res.body, {
message: "Hello, World!",
});
});
I tend to run AVA with the following shell command:
yarn ava --verbose --watch
I have a very simple user backend up and running (node, express, mongoose, mongo, etc) and with postman can verify when I add a user it works, when I request a login it works and get a token, and if I put in the wrong details it rejects it,
Now I used this git hub repo https://github.com/christiannwamba/vue-auth-vuex to spin up a simple frontend for this. Which I thought was all working fine as it appeared to be logging in until I found it was accepting whatever details I put in for the email and password as correct!
The backend server kept responding ok when I hit it with the vue app, but on closer inspection when I console logged what it was getting, which was null and returning user not found. So again I don't think there is anything wrong here.
Something I have noticed in chrome dev tools network, it is sending two versions of authenticate, first is empty and then the next one has responses.
I'm at a bit of a loss why it's sending empty requests first time and why it allows the login when it's getting a bad return.
Server.js file:
const express = require('express');
const logger = require('morgan');
const movies = require('./routes/movies') ;
const users = require('./routes/users');
const bodyParser = require('body-parser');
const mongoose = require('./config/database'); //database configuration
var jwt = require('jsonwebtoken');
var cors = require('cors')
const app = express();
// Add cors
app.use(cors());
app.options('*', cors()); // enable pre-flight
app.set('secretKey', 'nodeRestApi'); // jwt secret token
// connection to mongodb
mongoose.connection.on('error', console.error.bind(console, 'MongoDB connection error:'));
app.use(logger('dev'));
app.use(bodyParser.urlencoded({extended: false}));
app.get('/', function(req, res){
res.json({"api" : "User API"});
});
// public route
app.use('/users', users);
// private route
app.use('/movies', validateUser, movies);
app.get('/favicon.ico', function(req, res) {
res.sendStatus(204);
});
function validateUser(req, res, next) {
jwt.verify(req.headers['x-access-token'], req.app.get('secretKey'), function(err, decoded) {
if (err) {
res.json({status:"error", message: err.message, data:null});
}else{
// add user id to request
req.body.userId = decoded.id;
next();
}
});
}
// express doesn't consider not found 404 as an error so we need to handle 404 it explicitly
// handle 404 error
app.use(function(req, res, next) {
let err = new Error('Not Found');
err.status = 404;
next(err);
});
// handle errors
app.use(function(err, req, res, next) {
console.log(err);
if(err.status === 404)
res.status(404).json({message: "Not found"});
else
res.status(500).json({message: "Something looks wrong :( !!!"});
});
app.listen(3000, function(){
console.log('Node server listening on port 3000');
});
Update:
I have added in under my CORS bit in server.js:
app.options('/users/authenticate', function(req, res){
res.header('Access-Control-Allow-Origin', '*');
res.header('Access-Control-Allow-Methods', 'POST');
res.end();
});
In network I now only get the one request. Under form data it appears to be there but it's saying in the response that data is null, and even more odd the vuejs is still logging in and allowing access to the restricted pages.
Temporarily comment out the line where you set the headers to application/x-www-form-urlencoded. Then add app.use(bodyParser.json()) to your server.js and see if it works. What's happening is your request object is malformed, which is why the server cannot parse the request correctly.
Looks like CORS issue. If you run UI using a different server and your backend is running by itself, then your browser will send pre-flight request first which is an options request. That is the reason you see 2 authenticate requests in the developer tools. You can read more about this here
Why is an OPTIONS request sent and can I disable it?
my client side app.html:
<script src="/socket.io/socket.io.js"></script>
<script>
var socket = io.connect('http://mywebsite.com/');
socket.on('news', function (data) {
console.log(data);
socket.emit('my other event', { my: 'data' });
});
</script>
my serverside app.js:
var express = require('express')
, http = require('http');
var app = express();
var server = http.createServer(app);
var io = require('socket.io').listen(server);
server.listen(process.env.PORT || 8888);
app.get('/', function (req, res) {
res.sendfile(__dirname + '/app.html');
});
io.sockets.on('connection', function (socket) {
socket.emit('news', { hello: 'world' });
socket.on('my other event', function (data) {
console.log(data);
});
});
if I go to http://mywebsite.com:8888 the console reads:
Object {hello: "world"}
but if I go to http://mywebsite.com/app.html the console reads:
GET http://mywebsite.com/socket.io/socket.io.js 404 (Not Found)
Uncaught ReferenceError: io is not defined
as refired to here https://stackoverflow.com/a/10192084/990434 I have express v3.1.1, i've also tried this answer https://stackoverflow.com/a/14167488/990434 to no avail. I have tried some other random things from a google group that I can't seem to find in my long messy history. any idea how to fix this?
You are confusing files stored on your server and routes that your server serves. Although app.html is a file on your server, your server does not respond to requests to /app.html. Instead, when a request comes to /, then your server sends the file app.html. In fact, the client/recipient will never know that the file that it receives was called app.html on your server. So when a client (browser) goes to http://mywebsite.com:8888 then they get the html file that has the script tags. Those script tags have code that makes your browser connect to socket.io which is programmed to, on 'connection', send a message to the client:
socket.emit('news', { hello: 'world' });
The client code running in your browser then receives the message
socket.on('news', function (data) {
console.log(data);
and then sends a message back to the server
socket.emit('my other event', { my: 'data' });
Everything is working as programmed. However, there is no 'router' on your server listening to requests to /app.html