为什么这些 sinon 存根解析为未定义?

Why do these sinon stubs resolve to undefined?

我已经为以下代码编写了一个单元测试,并使用 Sinon(更具体地说:sinon-chrome,一个过时但仍在运行的浏览器方法(读取:web-extension API)存根我的用例库)。

/**
 * Returns an array of languages based on getAcceptLanguages and getUILanguage to use as defaults
 * for when no saved languages exist in browser storage.
 *
 * @memberof Helpers
 * @returns {array} Array of language codes i.e. ['en-US', 'fr']
 */
async function getDefaultLanguages () {
  const acceptedLanguages = await browser.i18n.getAcceptLanguages()
  const uiLanguage = browser.i18n.getUILanguage()

  return [uiLanguage].concat(acceptedLanguages)
}

单元测试:

const sinon = require('sinon')
const browser = require('sinon-chrome/extensions')
const { assert } = require('chai')
const helpers = require('../src/helpers')

// helpers that rely on the web-extension API (will need to be mocked)
describe('Helpers: Web-Extension API', function () {
  const { getDefaultLanguages } = helpers

  let languages

  before(async function () {
    global.browser = browser // need to patch global browser with mocked api
    browser.menus = browser.contextMenus // sinon-chrome doesn't wrap this method as it should
    
    sinon.stub(browser.i18n, 'getAcceptLanguages').resolves(['de-de', 'en-au'])
    sinon.stub(browser.i18n, 'getUILanguage').returns('en-en')

    languages = await getDefaultLanguages()
  })

  it('asserts that getDefaultLanguages() returns an array of strings', function () {
    assert.isTrue(languages.every(x => typeof x === 'string'))
  })

  it('asserts that getDefaultLanguages() includes UI and i18n languages', function () {
    assert.sameMembers(languages, ['de-de', 'en-en', 'en-au'])
  })
})

由于两个存根方法都返回未定义,测试失败,但是 Sinon docs state 很清楚 stub.resolves(value):

Causes the stub to return a Promise which resolves to the provided value.

When constructing the Promise, sinon uses the Promise.resolve method. You are responsible for providing a polyfill in environments which do not provide Promise. The Promise library can be overwritten using the usingPromise method.

由于节点内置了对 Promise 的支持,我希望上述存根可以使用指定的值(语言环境字符串数组和语言环境字符串)进行解析,但两者都 resolve/return 未定义。

非常感谢这方面的帮助!

原来 sinon-chrome,无论出于何种原因,需要在 运行 期间和测试 运行 之前注册 'i18n' 插件。

为什么 web-extensions API 的这个特定部分没有以与所有其他模拟相同的方式实现仍然是一个谜,但添加两行解决了问题并允许 sinon 存根工作正如预期的那样:

const sinon = require('sinon')
const browser = require('sinon-chrome/extensions')
const I18nPlugin = require('sinon-chrome/plugins').I18nPlugin // I18n plugin constructor
const { assert } = require('chai')
const helpers = require('../src/helpers')

// helpers that rely on the web-extension API (will need to be mocked)
describe('Helpers: Web-Extension API', function () {
  const { getDefaultLanguages } = helpers

  let languages

  before(async function () {
    global.browser = browser // need to patch global browser with mocked api
    browser.menus = browser.contextMenus // sinon-chrome doesn't wrap this method as it should
    browser.registerPlugin(new I18nPlugin()) // register the plugin on browser instance
    
    sinon.stub(browser.i18n, 'getAcceptLanguages').resolves(['de-de', 'en-au'])
    sinon.stub(browser.i18n, 'getUILanguage').returns('en-en')

    languages = await getDefaultLanguages()
  })
})
如果您尝试监视 non-existent 对象 属性,或者给定的 属性 不是函数,

Sinon-chrome 会抛出类型错误。不幸的是,它不会抛出任何错误,如果尝试存根 non-existent 对象 属性,它只是 returns 未定义,这似乎是一个糟糕的设计选择。

这就是为什么 returns()resolves() 存根在原始代码中都返回未定义的原因。