在 promise 解析时调用私有函数的单元测试模块

Unit test module that invokes private functions upon promise resolve

我是 Node 单元测试的新手,在承诺方面遇到了障碍。我的模块通过 ApiController 执行搜索,然后 returns 一个承诺。根据其解析状态,它调用模块中的两个私有函数之一。

模块:

module.exports.process = function(req, res) {
  let searchTerm = req.query.searchValue;

  ApiController.fetchResults(searchTerm)
    .then((results) => onReturnedResults(results), (err) => onNoResults());

  function onReturnedResults(results) {
    req.app.set('searchResults', results);
    res.redirect('/results');
  }

  function onNoResults() {
    res.render('search/search', { noResultsFound: true });
  }
};

测试:

var res = {
  viewName: '', data : {},
  render: function(view, viewData) {
    this.view = view;
    this.viewData = viewData;
  }
};

var req = { query: { searchValue: 'doesnotexist' } }

describe('When searching for results that do not exist', function() {
  it('should display a no results found message', function(done) {
    let expectedResult = {
      noResultsFound: true;
    }

    SearchController.process(req, res);

    // Assert...  
    expect(res.viewData).to.be.equal(expectedResult);
    done();
  });
})

关于此的最佳做法是什么?我如何才能 'mock' fetchResults 返回承诺,以便它实际上不会从 API 获取结果(同时仍然调用私有函数)?

如果方法的作用域为闭包或对模块私有,则无法访问它们,只能间接验证。要提供 fetchResults 的测试实现,必须以某种方式公开它。

一种方法是 require ApiController(这可能已经发生)然后使用 proxyquiremockery 来覆盖依赖关系。我与许多测试套件合作过,它们交互和提供测试依赖项的唯一方法是通过修补 require。使用这种方法,测试套件变成了一场噩梦。清理、取消打补丁、级联补丁,所有这些都会浪费大量时间,如果有任何其他选择,我会建议不要这样做。

另一种方法是将更改 fetchResults 实现的能力融入到您的对象中。除了测试之外,使 fetchResults 可配置将有助于将您的代码与未来的更改隔离开来,这有助于将对 process when/if 的影响降至最低,您需要获取结果的方式发生变化:

var Results = (function() {
  return {
    process: function(req, res) {
      let searchTerm = req.query.searchValue;

      this.fetchResults(searchTerm)
      .then((results) => this.onReturnedResults(req, res, results), (err) =>  this.onNoResults(res)),

    fetchResults: ApiController.fetchResults,     

    onReturnedResults: function(req, res, results) {
      req.app.set('searchResults', results);
      res.redirect('/results');
    },

    onNoResults: function(res) {
      res.render('search/search', { noResultsFound: true });
    }
  };

})()

(我有一段时间没有使用 javascript,所以我不确定对上述对象建模的最佳方法,但重要的部分是如果你的测试想要覆盖一个实现而不是必须公开实现,上面的代码应该这样做)

现在有一个 Results 对象,其 fetchResults 方法以一种易于覆盖的方式公开

var fakeFetchResults = sinon.stub();
fakeFetchResults.return(aFakePromiseWithResultsToTriggerResultsCodePath);
Results.fetchResult = fakeFetchResults;

上面公开了 return 函数和错误函数,它们还允许对该功能进行独立的单元测试。

使用过程是通过结果对象完成的,你的控制器可以注册

Results.process