Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I unit test a function that uses promises and event emitters in Node.js?

My question is about unit testing with promises and event emitters in Node.js. I am using the jasmine framework if that matters.

The code below uses the https module of Node.js to send a request to an API. The API will return JSON. The JSON from the API is the "rawData" variable in the code below.

I want to unit test that the function returns JSON (and not a JavaScript object).

I have unsuccessfully tried several approaches to unit testing that aspect of this function:

1) I tried spying on the Promise constructor so that it would return a fake function which would simply return a JSON string.

2) I have tried spying on the .on('eventType', callback) function of EventEmitters in Node.js to fake a function that returns JSON.

My question is: are either of the two approaches above possible and/or recommend for accomplishing my goal? Is there a different approach to isolating the http request and emitting of events from my unit test objective? Do I need to rewrite this function to facilitate easier unit testing?

 const https = require('https');

 function getJSON() {
  return new Promise((resolve, reject) => {
    const request = https.get(someConfig);
    request.on('response', resolve);
  })
  .then(msg => {
    return new Promise((resolve, reject) => {
      let rawData = '';
      msg.on('data', chunk => { rawData += chunk });
      msg.on('end', () => {
        resolve(rawData);
      });
    });
  })
  .then(json => {
    JSON.parse(json);
    return json;
  })
}
like image 568
Mr.C Avatar asked Dec 07 '25 05:12

Mr.C


2 Answers

Is there a reason you want to stick to https for making a request? If not, your code and your testing can both become really simple. I'll give an example using axios.

Http request can look like this

getJSON() {
const url = 'https://httpbin.org/get';
return axios
  .get(url)
  .then(response => response);

}

and you can stub the get call with Sinon

 lab.experiment('Fake http call', () => {
  lab.before((done) => {
    Sinon
      .stub(axios, 'get')
      .resolves({ data: { url: 'testUrl' } });
    done();
  });
  lab.test('should return the fake data', (done) => {
    const result = requestHelper.getJSON2();
    result.then((response) => {
      expect(response.data.url).to.eqls('testUrl');
      axios.get.restore();
      done();
    });
  });
});

With the existing code, nock would work like this

lab.experiment('Fake http call with nock', () => {
  lab.test('should return the fake data', (done) => {
    nock('https://httpbin.org')
      .get('/get')
      .reply(200, {
        origin: '1.1.1.1',
        url: 'http://testUrl',
      });
    const result = requestHelper.getJSON2();
    result.then((response) => {
      const result = JSON.parse(response);
      console.log(JSON.parse(response).url);
      expect(result.url).to.eqls('http://testUrl');
      nock.cleanAll();
      done();
    });
  });
});

Full code is here

like image 112
AbhinavD Avatar answered Dec 08 '25 17:12

AbhinavD


I would say that you need to refactor the code a little bit to be more testable.

When I write unit tests for functions I keep below points in mind

  1. You do not need to test for the inbuilt or library modules as they are already well tested.

  2. Always refactor your functions to have very specific reponsibility.

Implementing these two in your example, i would separate the server call in a service module whose sole responsibility is to take url (and configurations, if any) make server calls.

Now, when you do that you get two benefits 1. you have a reusable piece of code which you can now use to make other server calls(also makes your code cleaner and shorter)

  1. Since its a module you can now write seperate tests for that module and take the responsibility of checking whether server calls are made from your current module that uses it.

Now all thats left to test in your getJSON function is to spyOn that service module and use tohaveBeenCalledWith and check that data is properly parsed.You can mock the service to return your desired data.

1 its making a service call so test for toHaveBeenCalledWith

2 its parsing to JSON so test for valid/invalid JSON also test for failures

//no need to test whether https is working properly
//its already tested
 const https = require('https');
const service = require("./pathToservice");

 function getJSON() {
  return service.get(somConfig)
  .then(json => {
    JSON.parse(json);
    return json;
  })
}

//its cleaner now
//plus testable
like image 21
khawar jamil Avatar answered Dec 08 '25 18:12

khawar jamil



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!