135

My Issue

I've coded a very simple CRUD API and I've started recently coding also some tests using chai and chai-http but I'm having an issue when running my tests with $ mocha.

When I run the tests I get the following error on the shell:

TypeError: app.address is not a function

My Code

Here is a sample of one of my tests (/tests/server-test.js):

var chai = require('chai');
var mongoose = require('mongoose');
var chaiHttp = require('chai-http');
var server = require('../server/app'); // my express app
var should = chai.should();
var testUtils = require('./test-utils');

chai.use(chaiHttp);

describe('API Tests', function() {
  before(function() {
    mongoose.createConnection('mongodb://localhost/bot-test', myOptionsObj);
  });

  beforeEach(function(done) {
    // I do stuff like populating db
  });

  afterEach(function(done) {
    // I do stuff like deleting populated db
  });

  after(function() {
    mongoose.connection.close();
  });

  describe('Boxes', function() {

    it.only('should list ALL boxes on /boxes GET', function(done) {
      chai.request(server)
        .get('/api/boxes')
        .end(function(err, res){
          res.should.have.status(200);
          done();
        });
    });

    // the rest of the tests would continue here...

  });

});

And my express app files (/server/app.js):

var mongoose = require('mongoose');
var express = require('express');
var api = require('./routes/api.js');
var app = express();

mongoose.connect('mongodb://localhost/db-dev', myOptionsObj);

// application configuration
require('./config/express')(app);

// routing set up
app.use('/api', api);

var server = app.listen(3000, function () {
  var host = server.address().address;
  var port = server.address().port;

  console.log('App listening at http://%s:%s', host, port);
});

and (/server/routes/api.js):

var express = require('express');
var boxController = require('../modules/box/controller');
var thingController = require('../modules/thing/controller');
var router = express.Router();

// API routing
router.get('/boxes', boxController.getAll);
// etc.

module.exports = router;

Extra notes

I've tried logging out the server variable in the /tests/server-test.js file before running the tests:

...
var server = require('../server/app'); // my express app
...

console.log('server: ', server);
...

and I the result of that is an empty object: server: {}.

Penny Liu
  • 15,447
  • 5
  • 79
  • 98
charliebrownie
  • 5,777
  • 10
  • 35
  • 55

10 Answers10

323

You don't export anything in your app module. Try adding this to your app.js file:

module.exports = server
xersiee
  • 4,432
  • 1
  • 14
  • 23
64

It's important to export the http.Server object returned by app.listen(3000) instead of just the function app, otherwise you will get TypeError: app.address is not a function.

Example:

index.js

const koa = require('koa');
const app = new koa();
module.exports = app.listen(3000);

index.spec.js

const request = require('supertest');
const app = require('./index.js');

describe('User Registration', () => {
  const agent = request.agent(app);

  it('should ...', () => {
Kim Kern
  • 54,283
  • 17
  • 197
  • 195
  • 3
    You should include in your answer _why_ it's "important to export the `http.Server` object". – GrumpyCrouton Jun 09 '17 at 18:42
  • @GrumpyCrouton I added the error you will get otherwise – Kim Kern Jun 09 '17 at 22:35
  • 1
    Thank you Kim. I gave you a +1 because I think that does improve your answer. In the future, you should explain _why_. Imagine someone looking at this question and only has basic knowledge, a well thought out and explained answer will teach them a lot more. – GrumpyCrouton Jun 10 '17 at 03:52
  • For your tests you shoud *not* use app.listen() which starts an actual server, use http.createServer() instead – Whyhankee May 22 '19 at 07:00
34

This may also help, and satisfies @dman point of changing application code to fit a test.

make your request to the localhost and port as needed chai.request('http://localhost:5000')

instead of

chai.request(server)

this fixed the same error message I had using Koa JS (v2) and ava js.

Skyguard
  • 1,535
  • 14
  • 15
14

The answers above correctly address the issue: supertest wants an http.Server to work on. However, calling app.listen() to get a server will also start a listening server, this is bad practice and unnecessary.

You can get around by this by using http.createServer():

import * as http from 'http';
import * as supertest from 'supertest';
import * as test from 'tape';
import * as Koa from 'koa';

const app = new Koa();

# add some routes here

const apptest = supertest(http.createServer(app.callback()));

test('GET /healthcheck', (t) => {
    apptest.get('/healthcheck')
    .expect(200)
    .expect(res => {
      t.equal(res.text, 'Ok');
    })
    .end(t.end.bind(t));
});

Whyhankee
  • 822
  • 10
  • 14
  • Honestly I think this should be the accepted answer, calling app.listen() in the app will make you run into issues later. – 1011 1110 Apr 12 '21 at 01:46
  • The problem with this answer is that you create a separate app to run your tests, which doesn't include the actual code of your route handlers. How I've always solved this problem is simply have a function which accepts a param on whether to call `app.listen` or not and that function always returns the `app` object. – Catfish Dec 28 '21 at 02:50
4

Just in case, if someone uses Hapijs the issue still occurs, because it does not use Express.js, thus address() function does not exist.

TypeError: app.address is not a function
      at serverAddress (node_modules/chai-http/lib/request.js:282:18)

The workaround to make it work

// this makes the server to start up
let server = require('../../server')

// pass this instead of server to avoid error
const API = 'http://localhost:3000'

describe('/GET token ', () => {
    it('JWT token', (done) => {
       chai.request(API)
         .get('/api/token?....')
         .end((err, res) => {
          res.should.have.status(200)
          res.body.should.be.a('object')
          res.body.should.have.property('token')
          done()
      })
    })
  })
4

Export app at the end of the main API file like index.js.

module.exports = app;
Amir Astaneh
  • 2,152
  • 1
  • 20
  • 20
2

We had the same issue when we run mocha using ts-node in our node + typescript serverless project.

Our tsconfig.json had "sourceMap": true . So generated, .js and .js.map files cause some funny transpiling issues (similar to this). When we run mocha runner using ts-node. So, I will set to sourceMap flag to false and deleted all .js and .js.map file in our src directory. Then the issue is gone.

If you have already generated files in your src folder, commands below would be really helpful.

find src -name ".js.map" -exec rm {} \; find src -name ".js" -exec rm {} \;

Dilunika
  • 163
  • 3
  • 10
2

I am using Jest and Supertest, but was receiving the same error. It was because my server takes time to setup (it is async to setup db, read config, etc). I needed to use Jest's beforeAll helper to allow the async setup to run. I also needed to refactor my server to separate listening, and instead use @Whyhankee's suggestion to create the test's server.

index.js

export async function createServer() {
  //setup db, server,config, middleware
  return express();
}

async function startServer(){
  let app = await createServer();
  await app.listen({ port: 4000 });
  console.log("Server has started!");
}

if(process.env.NODE_ENV ==="dev") startServer();

test.ts

import {createServer as createMyAppServer} from '@index';
import { test, expect, beforeAll } from '@jest/globals'
const supertest = require("supertest");
import * as http from 'http';
let request :any;

beforeAll(async ()=>{
  request = supertest(http.createServer(await createMyAppServer()));
})

test("fetch users", async (done: any) => {
  request
    .post("/graphql")
    .send({
      query: "{ getQueryFromGqlServer (id:1) { id} }",
    })
    .set("Accept", "application/json")
    .expect("Content-Type", /json/)
    .expect(200)
    .end(function (err: any, res: any) {
      if (err) return done(err);
      expect(res.body).toBeInstanceOf(Object);
      let serverErrors = JSON.parse(res.text)['errors'];
      expect(serverErrors.length).toEqual(0);
      expect(res.body.data.id).toEqual(1);
      done();
    });
});

Edit:

I also had errors when using data.foreach(async()=>..., should have use for(let x of... in my tests

James L.
  • 12,893
  • 4
  • 49
  • 60
0

You have to export the app not the server. Supertest takes the app, not the server.

module.exports = app;
Devanand Sharma
  • 129
  • 1
  • 6
0

For those who for some reason came here for Fastify solution, it's

import { app } from "../index.js"; // <-- the app, not object returned from .listen()

beforeAll(async () => {
    // wait until the server actually starts, otherwise eat weird errors
    await app.ready();
});

test('your great test', async () => {
    const agent = supertest.agent(app.server); // <-- pass the server, not the app
    // .....your tests here
});
grreeenn
  • 2,287
  • 1
  • 20
  • 27