Mitch
Mitch

Reputation: 765

How to mock required files in Node.js?

I'm trying to mock a node module so that it will return mocked data. However in my tests it is still making an actual call. Considering the following code:

const makeRequestStructure = require('./modules/makeRequestStructure.js').makeRequestStructure
const normalizeFinalResponse = require('./modules/normalizeFinalResponse.js').normalizeFinalResponse
const doARequest = require('./modules/doARequest.js').doARequest

exports.addPost = (event) => {
  const requestStructure = makeRequestStructure('POST', '/posts')

  const requestPostData = {
    title: event.body.title,
    content: event.body.content
  }

  return doARequest(requestStructure, requestPostData).then((res) => {
    const finalResponse = normalizeFinalResponse(200, res)
    return finalResponse
  }).catch((err) => {
    const finalResponse = normalizeFinalResponse(400, err)
    return finalResponse
  })
}

I have the following test file:

const mock = require('mock-require')
const sinon = require('sinon')
const expect = require('chai').expect

const addPost = require('../addPost.js')

describe('the addPost API call', () => {
  beforeEach(() => {
    mock('../modules/doARequest', { doARequest: function() {
      return Promise.resolve({})
    }})
  });
  it('Returns with a statusCode of 200', () => {
    const event = { body: { title: 'Lorem ipsum', content: 'Lorem ipsum dolor sit amet' } }
    const expectedReturn = { id: 20000000000000 }
    return addPost.addPost(event).then((res) => {
      expect(res.body.message).to.eql(expectedReturn)
    })
  });
})

This test is making an actual call to https://jsonplaceholder.typicode.com/posts and it returns { id: 101 }. However i want it to return { id: 20000000000000 }. I have tried to mock the request using Nock. However the hostname is defined in a .env file and could be different depending on the server the script runs on.

The doARequest.js file looks like this:

const https = require('https')

module.exports.doARequest = function (params, postData) {
  return new Promise((resolve, reject) => {
    const req = https.request(params, (res) => {
      let body = []
      res.on('data', (chunk) => {
        body.push(chunk)
      })
      res.on('end', () => {
        try {
          body = JSON.parse(Buffer.concat(body).toString())
        } catch (e) {
          reject(e)
        }
        resolve(body)
      })
    })
    req.on('error', (err) => {
      reject(err)
    })
    if (postData) {
      req.write(JSON.stringify(postData))
    }
    req.end()
  })
}

What am i doing wrong here? Any help will be appreciated.

Upvotes: 3

Views: 2777

Answers (2)

eol
eol

Reputation: 24565

As an alternative to estus solution with reRequiring the module, you could use "dependency injection".

The problem is that you're directly depending on the doARequest.js-module in addPost.js, thus making it difficult to mock its behaviour. This is where dependency injection comes in handy, since you can just pass a mocked doARequest-module in your unit test:

// addPost.js
exports.addPost = (event, doARequest) => {
    const requestStructure = makeRequestStructure('POST', '/posts')

    const requestPostData = {
        title: event.body.title,
        content: event.body.content
    }

    return doARequest(requestStructure, requestPostData).then((res) => {
        const finalResponse = normalizeFinalResponse(200, res)
        return finalResponse
    }).catch((err) => {
        const finalResponse = normalizeFinalResponse(400, err)
        return finalResponse
    })
}

// addPost.test.js
describe('the addPost API call', () => {        
    it('Returns with a statusCode of 200', () => {
        const mockedDoARequest = () => Promise.resolve({ "whateverResponseYouLike": "12345" });
        const event = { body: { title: 'Lorem ipsum', content: 'Lorem ipsum dolor sit amet' } }
        const expectedReturn = { id: 20000000000000 }
        return addPost.addPost(event, mockedDoARequest).then((res) => {
            expect(res.body.message).to.eql(expectedReturn)
        })
    });
})

Everywhere else you'd pass the real module, i.e.

const doARequest = require('./modules/doARequest.js').doARequest
...
return addPost.addPost(event, doARequest).then(...)

Upvotes: 1

Estus Flask
Estus Flask

Reputation: 223104

CommonJS modules produce singleton export objects. Once a module is imported, it isn't evaluated. mock(...) doesn't affect anything because original doARequest module was evaluated when addPost was imported.

mock-require provides a way to re-evaluate modules. addPost shouldn't be imported at the top of test file. Instead, it should be imported inside a test where it's used:

const addPost = mock.reRequire('../addPost.js');

Upvotes: 3

Related Questions