Isaac Vidrine
Isaac Vidrine

Reputation: 1666

How to export node express app for chai-http

I have an express app with a few endpoints and am currently testing it using mocha, chai, and chai-http. This was working fine until I added logic for a pooled mongo connection, and started building endpoints that depended on a DB connection. Basically, before I import my API routes and start the app, I want to make sure I'm connected to mongo.

My problem is that I'm having trouble understanding how I can export my app for chai-http but also make sure there is a DB connection before testing any endpoints.

Here, I am connecting to mongo, then in a callback applying my API and starting the app. The problem with this example is that my tests will start before a connection to the database is made, and before any endpoints are defined. I could move app.listen and api(app) outside of the MongoPool.connect() callback, but then I still have the problem of there being no DB connection when tests are running, so my endpoints will fail.

server.js

import express from 'express';
import api from './api';
import MongoPool from './lib/MongoPool';
let app = express();
let port = process.env.PORT || 3000;
MongoPool.connect((err, success) => {
    if (err) throw err;
    if (success) {
        console.log("Connected to db.")
        // apply express router endpoints to app
        api(app);
        app.listen(port, () => {
            console.log(`App listening on port ${port}`);
        })
    } else {
        throw "Couldnt connect to db";
    }

})
export default app;

How can I test my endpoints using chai-http while making sure there is a pooled connection before tests are actually executed? It feels dirty writing my application in a way that conforms to the tests I'm using. Is this a design problem with my pool implementation? Is there a better way to test my endpoints with chai-http?

Here is the test I'm running

test.js

let chai = require('chai');
let chaiHttp = require('chai-http');
let server = require('../server').default;;
let should = chai.should();


chai.use(chaiHttp);
//Our parent block
describe('Forecast', () => {
/*
  * Test the /GET route
  */
  describe('/GET forecast', () => {
      it('it should GET the forecast', (done) => {
        chai.request(server)
            .get('/api/forecast?type=grid&lat=39.2667&long=-81.5615')
            .end((err, res) => {
                res.should.have.status(200);
              done();
            });
      });
  });

});

And this is the endpoint I'm testing

/api/forecast.js

import express from 'express';
import MongoPool from '../lib/MongoPool';
let router = express.Router();
let db = MongoPool.db();

router.get('/forecast', (req, res) => {
    // do something with DB here
})

export default router;

Thank you for any help

Upvotes: 9

Views: 2769

Answers (7)

Ganesh Bhosale
Ganesh Bhosale

Reputation: 2120

For Chai 5 given solutions won't work. Chai 5 cannot be imported as a default as it does not have default export. You can use chai 5.1 and chai-http 5.1 as below in your test files.

test.setup.mjs

process.env.NODE_ENV = 'test';

import { expect } from 'chai';
global.expect = expect;
import sinon from 'sinon';
global.sinon = sinon;
import app from './app.js';

console.log('TESTING:   Test setup started');

await app.init();

console.log('TESTING:   Test setup completed');

Optional: You can use async await while initializing App (Mongoose & Express). This will make sure that your app is initialized before starting test cases.

test.mjs

import { expect, use } from 'chai';
import { default as chaiHttp, request } from 'chai-http';

use(chaiHttp);

// execute method will take express app as input and returns ChaiHttp.Agent
req = request.execute(expressApp);

// Use req object to request API calls on app
req.get('/')

If you are using commonJS for your project and haven't specified package.json type as module, then you will have to write test cases in .mjs files as I have done.

Upvotes: 0

Isaac Vidrine
Isaac Vidrine

Reputation: 1666

After receiving some good feedback, I found this solution works best for me, based on Gomzy's answer and Vikash Singh's answer.

In server.js I'm connecting to the mongo pool, then emitting the 'ready' event on the express app. Then in the test, I can use before() to wait for 'ready' event to be emitted on the app. Once that happens, I'm good to start executing the test.

server.js

import express from 'express';
import bodyParser from 'body-parser';
import MongoPool from './lib/MongoPool';
let app = express();
let port = process.env.PORT || 5000;
app.use(bodyParser.urlencoded({ extended: false }));
app.use(bodyParser.json());

(async () => {
    await MongoPool.connect();
    console.log("Connected to db.");
    require('./api').default(app);
    app.listen(port, () => {
        console.log(`Listening on port ${port}.`)
        app.emit("ready");
    });
})();

export default app;

test.js

//Require the dev-dependencies
import chai from 'chai';
import chaiHttp from 'chai-http';
import server from '../src/server'; 
let should = chai.should();
chai.use(chaiHttp);

before(done => {
  server.on("ready", () => {
    done();
  })
})
describe('Forecast', () => {
  describe('/GET forecast', () => {
    it('it should GET the forecast', (done) => {
      chai.request(server)
          .get('/api/forecast?type=grid&lat=39.2667&long=-81.5615')
          .end((err, res) => {
              res.should.have.status(200);
            done();
          });
    });
  });

});

Upvotes: 5

hoangdv
hoangdv

Reputation: 16147

You can use running server instead of a express instance.

Start your server with a private port, then take tests on the running server.

ex: PORT=9876 node server.js

In your test block, use chai.request('http://localhost:9876') (replace with your protocol, server ip...) instead of chai.request(server).

Upvotes: 1

1565986223
1565986223

Reputation: 6718

If you're using native mongodb client you could implement reusable pool like:

MongoPool.js
// This creates a pool with default size of 5
// This gives client; You can add few lines to get db if you wish
// connection is a promise
let connection;
module.exports.getConnection = () => {
  connection = MongoClient(url).connect()
}

module.exports.getClient = () => connection

Now in your test you could,
const { getConnection } = require('./MongoPool')
...
describe('Forecast', () => {
  // get client connection
  getConnection()
  ...

In your route:
...
const { getClient } = require('./MongoPool')
router.get('/forecast', (req, res) => {
    // if you made sure you called getConnection() elsewhere in your code, client is a promise (which resolves to mongodb connection pool)
    const client = getClient()
    // do something with DB here
    // then you could do something like client.db('db-name').then(//more).catch()
})

Upvotes: 0

Gautam V.
Gautam V.

Reputation: 536

Express app is an instance of EventEmitter so we can easily subscribe to events. i.e app can listen for the 'ready' event.

Your server.js file will look like below,

import express from 'express';
import api from './api';
import MongoPool from './lib/MongoPool';
let app = express();
let port = process.env.PORT || 3000;

app.on('ready', function() {
  app.listen(3000, function() {
    console.log('app is ready');
  });
});

MongoPool.connect((err, success) => {
  if (err) throw err;
  if (success) {
    console.log('Connected to db.');
    // apply express router endpoints to app
    api(app);

    // All OK - fire (emit) a ready event.
    app.emit('ready');
  } else {
    throw 'Couldnt connect to db';
  }
});

export default app;

Upvotes: 2

Vikash_Singh
Vikash_Singh

Reputation: 1896

Use Before function in your tests like below :

 describe('Forecast', () => {
  before(function(done){
   checkMongoPool(done); // this function should wait and ensure mongo connection is established.
  });
  it('/GET forecast', function(cb){
  // write test code here ...
  });
});

And you can check mongodb connection like this below methods:

Method 1: just check the readyState property -

mongoose.connection.readyState == 0; // not connected
mongoose.connection.readyState == 1; // connected`

Method 2: use events

mongoose.connection.on('connected', function(){});
mongoose.connection.on('error', function(){});
mongoose.connection.on('disconnected', function(){});

Upvotes: 1

Muhammad Aadil Banaras
Muhammad Aadil Banaras

Reputation: 1284

Just create a function below to connect to mongo and, make it returns a promise. then use await to wait for it to connect and return. the function could be like that

function dbconnect(){
    return new Promise(function(resolve, reject){

    MongoPool.connect((err, success) => {
    if (err) reject(err);
    if (success) {
        resolve({'status' : true})
    } else {
        reject(new Error({'status' : false}))
    }

})
    })
}

And then, use

await dbconnect();
api(app);
app.listen(port, () => {
    console.log(`App listening on port ${port}`);
})

now await line will wait for the function to connect to DB and then return success or error in case of failure. This is a kind of solution you can use, but I would not recommend you to do this, what we actually do is.

create services and use those services in routes, don't write DB code directly in routes.

and

while writing tests for routes mock/stub those services, and test services separately in other test cases, where you just pass DB object and service will add functions on that DB objects, so in tests you can connect to DB and pass that object to those services to test functions, it will give you additional benefit, if you want to use dummy/test DB for testing you can set that in test cases.

Upvotes: 1

Related Questions