-1

I am running into issues when testing my express application. All tutorials use app.listen instead of https.createServer and I don't know how to properly use testing frameworks with the latter. The code:

test_node/app.js

var express = require("express");
const app = express();

app.set('port', process.env.SERVER_PORT || 443);
app.get('/', function(req, res){
    res.status(200).send('OK');
});

module.exports = {
    app: app
}

test_node/server.js

var https = require('https');
var fs = require('fs');
const app = require('./app');

let serverOptions = {
    key: fs.readFileSync('./cert/server.key'),
    cert: fs.readFileSync('./cert/server.cert')
}
const server = https.createServer(serverOptions, app)
server.listen(app.get('port'), function () {
    console.log(`Express HTTPS server listening on port ${app.get('port')}`);
});

async function handleSignal(signal) {
    console.log(`Received ${signal}`);
    server.close();
    process.exit();
}
process.on('SIGINT', handleSignal);
process.on('SIGTERM', handleSignal);
process.on('uncaughtException', handleSignal);

test_node/test/test.js

const request = require('supertest')

describe("Test the root path", () => {
    let server;
    beforeAll(function () {
        server = require('../app');
    });
    afterAll(function () {
        server.close();
    });

    test("It should respond to the GET method", async () => {
        const response = await request(server).get("/");
        expect(response.statusCode).toBe(200);
    });
});

test_node/package.json

{
  "name": "test_node",
  "version": "1.0.0",
  "description": "",
  "main": "server.js",
  "scripts": {
    "start": "node server.js",
    "test": "jest"
  },
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "jest": "^26.6.3",
    "supertest": "^6.1.3"
  },
  "dependencies": {
    "express": "^4.17.1"
  }
}

The error I get when I test the above code with jest (jest --runInBand test.js):

TypeError: app.address is not a function
    at Test.Object.<anonymous>.Test.serverAddress (C:\code\test_node\node_modules\supertest\lib\test.js:57:18)
    at new Test (C:\code\test_node\node_modules\supertest\lib\test.js:38:12)
    at Object.get (C:\code\test_node\node_modules\supertest\index.js:27:14)
    at Object.<anonymous> (C:\code\test_node\test\test.js:13:48)
    at Object.asyncJestTest (C:\code\test_node\node_modules\jest-jasmine2\build\jasmineAsyncInstall.js:106:37)
    at C:\code\test_node\node_modules\jest-jasmine2\build\queueRunner.js:45:12
    at new Promise (<anonymous>)
    at mapper (C:\code\test_node\node_modules\jest-jasmine2\build\queueRunner.js:28:19)
    at C:\code\test_node\node_modules\jest-jasmine2\build\queueRunner.js:75:41

This is obviously a stripped down version of my code. I also create MongoDB connections at start, but I removed them for simplicity. Ideally I want to use async/await instead of promises for creating my server (and MongoDB connections) before my tests start. I also need to close those connections after tests end or fail. In your answer you can safely assume I'm JS beginner :).

Additional info

I'm using node '14.15.4' on Windows 10 x64. Webstorm IDE.

Tried various combinations of the following:

  • combining app and server in one file (see below)
  • creating the server in a separate function and exporting it
  • using mocha/chai instead of jest
  • testing a stripped down version of the app

Errors I get depending on the combinations I tried:

  • TypeError: app.address is not a function
  • TypeError: app.get is not a function
  • EADDRINUSE: address already in use ::::443
  • various other errors with timeouts, wrong arguments, etc.

Relevant topics that didn't help (or I overlooked something):

Context

I included the server.js file above, although the test doesn't use it, because my server application should also be able to start normally when I run node server.js. With the above code it doesn't start and I get the following error:

events.js:112
    throw new ERR_INVALID_ARG_TYPE('listener', 'Function', listener);
    ^

TypeError [ERR_INVALID_ARG_TYPE]: The "listener" argument must be of type function. Received an instance of Object
    at checkListener (events.js:112:11)
    at _addListener (events.js:348:3)
    at Server.addListener (events.js:406:10)
    at new Server (https.js:71:10)
    at Object.createServer (https.js:91:10)
    at Object.<anonymous> (C:\code\test_node\server.js:9:22)
    at Module._compile (internal/modules/cjs/loader.js:1063:30)
    at Object.Module._extensions..js (internal/modules/cjs/loader.js:1092:10)
    at Module.load (internal/modules/cjs/loader.js:928:32)
    at Function.Module._load (internal/modules/cjs/loader.js:769:14) {
  code: 'ERR_INVALID_ARG_TYPE'
}

However, if I put all code in the same app.js file and I run node app.js, then the app starts fine, although the test produces the same error.

test_node/app.js

var https = require('https');
var fs = require('fs');
var express = require("express");
var server;
const app = express();

app.set('port', process.env.SERVER_PORT || 443);
app.get('/', function(req, res){
    res.status(200).send('OK');
});

async function handleSignal(signal) {
    console.log(`Received ${signal}`);
    server.close();
    process.exit();
}
process.on('SIGINT', handleSignal);
process.on('SIGTERM', handleSignal);
process.on('uncaughtException', handleSignal);

let serverOptions = {
    key: fs.readFileSync('./cert/server.key'),
    cert: fs.readFileSync('./cert/server.cert')
}
server = https.createServer(serverOptions, app).listen(app.get('port'), function () {
        console.log(`Express HTTPS server listening on port ${app.get('port')}`);
    });

module.exports = {
    app: app,
    server: server
}

To clarify, my question is how to fix the error from the jest test and not about the error when I run node server.js - the latter is just for context.

Mike
  • 393
  • 1
  • 3
  • 11
  • why are you testing express? – Lawrence Cherone Mar 07 '21 at 11:02
  • It seems you've done a lot of research, which is good, but that makes it difficult to tell what specific combination is giving what specific error. I'd expect splitting apart the app and server, and testing *only the former* with supertest, to work. – jonrsharpe Mar 07 '21 at 11:06
  • @LawrenceCherone I am testing my express server app, not express in general. – Mike Mar 07 '21 at 11:32
  • @jonrsharpe please suggest how I need to do this. I have tried various combinations, as mentioned, and get different errors. The question would become too long if I post all combinations I made. – Mike Mar 07 '21 at 11:34
  • Please give a [mre] for the split between app and server, with the specific error you're seeing in it. It works fine for e.g. https://github.com/textbook/starter-kit/blob/4d842b9f278e95fba5bdf43e7a262e461ee9e945/server/app.test.js?ts=2#L6-L8 – jonrsharpe Mar 07 '21 at 11:36
  • @jonrsharpe my original example was minimal and reproducible. But as you suggested, I added another example where I split the app and server, indicating the error I got. Note that the example you link is using ES6 syntax and not CommonJS. I want to understand how to fix my error under CommonJS syntax. – Mike Mar 07 '21 at 12:05
  • In your new example, is server involved at all? If not, as the require from app implies, it's not minimal. Can you give the actual traceback beyond the error? Have you checked (log/debug) that app *is* the app? You're exporting an *object*. And you don't need to leave "edit"s lying around; if people want to see how the question evolved they can do so in the history, it's easier for the next reader to – jonrsharpe Mar 07 '21 at 12:09
  • @jonrsharpe I re-wrote the question according to your suggestion. Please clarify how to check whether the app is the app and what the problem is with exporting an object. As mentioned, I'm a beginner. :) – Mike Mar 07 '21 at 13:38
  • 1
    Thanks! Again, you can log it (`console.log(app)`) or use a debugger (instructions vary by IDE). There's no problem with exporting an object *per se*, but in the files that import from app.js you assume you exported the app itself, not an object containing it. That's why it doesn't work from the tests or the server. – jonrsharpe Mar 07 '21 at 13:41
  • So you are telling me that I lost 2 days in figuring out that I had to use `require('../app').app` instead of `require('../app')`? :P Your comment helped me find the solution to both errors. Please post the solution itself as an answer, so I can mark it as resolved and it can help others. If you like, you can also mention how to export the app itself, instead of an object containing the app (using CommonJS syntax). BTW, not sure why my question was downvoted - it's a legitimate question to which I didn't find the answer in the referenced links, and I took care to format it correctly... – Mike Mar 07 '21 at 14:13

1 Answers1

1

As @jonrsharpe pointed out in the comments, I was assuming that with module.exports I export the app itself, but in fact I export an object containing the app. Therefore to fix the test error, I had to simply write in my test.js:

server = require('../app').app;

instead of server = require('../app');. Similarly, in server.js I had to write:

const app = require('./app').app;

This fixes both errors I mentioned in my question (TypeError: app.address is not a function).

Credits to @jonrsharpe for helping me find the solution!

Mike
  • 393
  • 1
  • 3
  • 11