【问题标题】:How can I unit test a function that uses promises and event emitters in Node.js?如何对在 Node.js 中使用 Promise 和事件发射器的函数进行单元测试?
【发布时间】:2018-09-12 07:19:00
【问题描述】:

我的问题是关于在 Node.js 中使用 Promise 和事件发射器进行单元测试。如果这很重要,我正在使用 jasmine 框架。

以下代码使用 Node.js 的 https 模块向 API 发送请求。 API 将返回 JSON。 API 中的 JSON 是下面代码中的“rawData”变量。

我想对函数返回 JSON(而不是 JavaScript 对象)进行单元测试。

我尝试了几种方法对该功能的该方面进行单元测试,但均未成功:

1) 我尝试监视 Promise 构造函数,以便它返回一个假函数,该函数只会返回一个 JSON 字符串。

2) 我尝试监视 Node.js 中 EventEmitters 的 .on('eventType', callback) 函数来伪造一个返回 JSON 的函数。

我的问题是:上述两种方法中的任何一种都可能和/或推荐用于实现我的目标吗?是否有不同的方法可以从我的单元测试目标中隔离 http 请求和事件的发射?我是否需要重写此函数以方便单元测试?

 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;
  })
}

【问题讨论】:

标签: javascript node.js unit-testing promise eventemitter


【解决方案1】:

您是否有理由坚持使用https 提出请求?如果没有,您的代码和测试都会变得非常简单。我将举一个使用axios的例子。

Http请求可以是这样的

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

}

您可以使用Sinon 存根get 调用

 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();
    });
  });
});

使用现有代码,nock 将像这样工作

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();
    });
  });
});

完整代码是here

【讨论】:

  • 您的回答确实解决了我的问题,但我正在尝试尽可能多地使用 Node.js API 并避免依赖。
【解决方案2】:

我会说您需要稍微重构代码以使其更具可测试性。

当我为函数编写单元测试时,我会牢记以下几点

  1. 您无需测试内置或库模块,因为它们已经过良好测试。

  2. 始终重构您的函数,使其具有非常具体的职责。

在您的示例中实现这两个,我将在一个服务模块中分离服务器调用,该模块的唯一职责是获取 url(和配置,如果有的话)进行服务器调用。

现在,当您这样做时,您将获得两个好处 1. 你有一段可重用的代码,你现在可以用它来进行其他服务器调用(也使你的代码更简洁)

  1. 由于它是一个模块,您现在可以为该模块编写单独的测试,并负责检查服务器调用是否来自您当前使用它的模块。

现在在您的 getJSON 函数中剩下的所有测试就是监视该服务模块并使用 tohaveBeenCalledWith 并检查数据是否已正确解析。您可以模拟该服务以返回您想要的数据。

1 它进行服务调用 所以测试 toHaveBeenCalledWith

2 解析为 JSON 所以测试有效/无效的 JSON 也测试失败

//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

【讨论】:

    【解决方案3】:

    我认为你没有成功,因为你是这样直接返回的。应该是这样的:

    function getJSON(callback) {
      (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);
            callback(json);
          })
        }
       // to use this: 
       getJSON((your_json)=> {
         // handling your json here.
       })
    

    您可以使用 child_process 生成测试服务器以提供 JSON API。示例:

    const { spawn } = require('child_process');
    const expect = chai.expect;
    const env = Object.assign({}, process.env, { PORT: 5000 });
    const child = spawn('node', ['test-api.js'], { env });
    child.stdout.on('data', _ => {
     // Make a request to our app
     getJSON((foo)=>{
      // your asserts go here.
      expect(foo).to.be.a('object');
      expect(foo.some_attribute).to.be.a('string')
      // stop the server
      child.kill();
     });
    });
    

    您可以在测试环境中自定义 someConfig 变量以指向“http://127.0.0.1:5000”。您的 test-api.js 文件是一个简单的 nodejs 脚本,它总是为每个请求响应预期的 JSON。

    更新单元测试示例

    【讨论】:

    • 使用 nock 可能会更容易。
    • 您的回答提供了有关模拟 API 和修改我的代码的信息,但它没有明确提及如何编写对我的问题很重要的单元测试。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-01-27
    • 2018-05-13
    • 2014-04-01
    • 1970-01-01
    • 2020-08-01
    • 2016-01-13
    相关资源
    最近更新 更多