Skip to content

Instantly share code, notes, and snippets.

@trojanh
Last active March 2, 2023 23:31
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save trojanh/18bb2fa9d5ef5f2b5af99655d9792892 to your computer and use it in GitHub Desktop.
Save trojanh/18bb2fa9d5ef5f2b5af99655d9792892 to your computer and use it in GitHub Desktop.
Issues Setting Up Jest, Nock, Supertest for Testing Nodejs with External API call

To prevent Supertest from throwing following error

port already in use

Add following line in app.js or main express server file

  if (NODE_ENV !== 'test') {
      await app.listen(PORT)
  }

ref: ladjs/supertest#568 (comment)

When setting up mongodb for testing can prevent mongo to connect to actual DB

  if (NODE_ENV !== 'test') {
      await mongoose.connect(CONNECTION_URI, OPTIONS)
  }

To setup env variables in JEST for testing env dependent logic

  const OLD_ENV = process.env;

  beforeEach(() => {
    jest.resetModules() // most important - it clears the cache
    process.env = { ...OLD_ENV }; // make a copy
  });

  afterAll(() => {
    process.env = OLD_ENV; // restore old env
  });

ref: https://stackoverflow.com/a/48042799/3861525

Setting Up Mongodb using mongodb-memory-server

import { MongoMemoryServer } from 'mongodb-memory-server'
import mongoose from 'mongoose'

const mongod = new MongoMemoryServer()

// can use it later in `beforeAll` callback
export async function initDB() {
  const uri = await mongod.getConnectionString()

  const mongooseOpts = {
    useNewUrlParser: true,
    autoReconnect: true,
    reconnectTries: Number.MAX_VALUE,
    reconnectInterval: 1000
  }

  await mongoose.connect(uri, mongooseOpts)
}

// can use it later in `afterAll` callback
export async function closeDB() {
  await mongoose.connection.dropDatabase()
  await mongoose.connection.close()
  await mongod.stop()
}

// can use it later in `afterEach` callback
export async function cleanupDB() {
  const collections = mongoose.connection.collections

  for (const key in collections) {
    const collection = collections[key]
    await collection.deleteMany()
  }
}

To record external API and mock it for next request use

In this case, sendSms API calls external API like AWS SNS or Twilio using the server endpoint use nock.cleanAll() in afterAll callback and nock.restore() in afterEach callback to make sure there are no side effects*

it('should return success with valid params', async (done) => {
  // setup path for fixtures for recorded data
    nock.back.fixtures = path.join(__dirname, 'fixtures') 
    ...
    ...
    nock.back.setMode('record')
    const { nockDone } = await nock.back('aws-sms-request.json')

  // to prevent recording localhost network request, can use it in `beforeAll` or `beforeEach` 
    nock.enableNetConnect(/127\.0\.0\.1/) 
    await request(app)
      .post('/sendSms')
      .send({
        phoneNumber: ['1234567890'],
        message: '123'
      }) 

  // to stop recording network requests using nock
      await nockDone() 
})

To make sure the jest it block is properly restored or cleared after the run use done() like below

it('should return success with valid params', async (done) => {
    await request(app)
      .post('/sendSms')
      .send({
        phoneNumber: ['1234567890'],
        message: '123'
      }) 
      done()
})

To mock imported function in the route

For instance we have a Crypto module used in route sendSms which generates random encrypted data when we call Crypto.encrypt() method then the response of the API endpoint will vary for every input, to ensure our tests are passing for such cases we can mock Crypto.encrypt in jest like below

import { Crypto } from '../src/helpers/Crypto'

const originalCrypto = Crypto.prototype.encrypt
// can be called in the `it` callback or beforeEach callback
function mockCrypto(value) {
  Crypto.prototype.encrypt = jest.fn(() => value)
}

function clearEncryptoMock() {
  Crypto.prototype.encrypt = originalEncrypto
}

To test Date functions

  
  function mockDateManager(defaultDate) {
    const defaultDate = Date.now
    
    return {
      mockDate: () =>  Date.now = () => defaultDate
      restoreDate: () => Date.now = defaultDate
  }
  const dateManager = mockDateManager(1595399185353)
  ...
  // can use it in individual tests for more control
  beforeEach(dateManager.mockDate)
  afterEach(dateManager.restoreDate)
  

Using imports with esm in test files using mocha

// package.json
"test": "mocha --recursive './test/*.js' --require esm"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment