混淆模拟无状态 React 组件辅助函数与 sinon、enyzme 和 ES6 导入

Confusion with mocking stateless React component helper functions with sinon, enyzme, and ES6 imports

我目前正在尝试对 React 组件进行单元测试,并且 运行 对模拟函数和辅助函数有些困惑。该模块看起来像这样

export const someHelper = () => {
  return ( <div></div> )
}

const MyComponent = () => {
  return (
    <span>
      {someHelper()}
    </span>
  )
}
export default MyComponent

然后这是测试文件的样子

import chai, { expect } from 'chai'
import chaiEnzyme from 'chai-enzyme'
import sinon from 'sinon'
import { shallow } from 'enzyme'
import React from 'react'

import MyComponent, { someHelper } from './MyComponent'

describe('MyComponent test', function (){
  it.only('should call someHelper once', function () {
    let spy = sinon.spy(someHelper)
    let myComponent = shallow(
      <MyComponent />
    )
    expect(spy.callCount).to.equal(1)
  })
})

但是由于 callCount 等于 0,此测试失败了。我想这可能与 someHelper 函数的上下文有关,所以我进行了这些更改

export const helpers = {
  someHelper () {
    ...
  }
}
const MyComponent = () => {
   ...
   {helpers.someHelper()}
   ...
}

_

import MyComponent, { helpers } ...

describe(...{
  it(...{
    let spy = sinon.spy(helpers.someHelper)
     ...
  })
})

但是这个测试仍然失败,callCount 等于 0。然后我做了这些更改

describe(...{
  it(...{
    let spy = sinon.spy(helpers, 'someHelper')
     ...
  })
})

然后测试现在通过了。

为什么我必须将 someHelper 附加到 helpers 对象才能使此测试生效?当 sinon docs 显示 spy(myFunc) 选项时,为什么我必须使用最后一个 spy(object, 'myfunc') 方法?

Why do I have to attach someHelper to the helpers object for this test to work?

Sinon 必须能够用它的 spy/stub-wrapped 版本替换对现有函数的引用,并且只有当该引用存储在对象中时才能这样做(helpers这种情况)。

它基本上是这样做的:

let functionToSpyOn = helpers.someHelper;
let spy = sinon.spy(functionToSpyOn);
helpers.someHelper = spy;

这里更复杂的是原始代码必须通过这个引用调用函数,所以这样的事情也行不通:

const someHelper     = () => { ... }
export const helpers = { someHelper }
const MyComponent    = () => {
   ...
   {someHelper()}
   ...
}

原因是 MyComponent 没有使用存储在 helpers 中的引用,后者将被 Sinon 取代。这就是组件需要使用 helpers.someHelper().

的原因

And why do I have to use that last spy(object, 'myfunc') method...

这又与用包装版本替换函数有关。

...when the sinon docs show a spy(myFunc) option?

我发现这个成语一般用处有限。它不会像您想象的那样监视对 myFunc 的所有调用,除非 这些调用是对作为 spy() 结果的间谍对象进行的。

例如:

let callback = (err, result) => { ... }
...
let spy = sinon.spy(callback);
someFuncToTest(spy);
expect(spy.callCount).to.equal(1);

所以不是直接传递回调函数,而是传递间谍。

问题不是 React 或 Sinon 特有的,而是一般 JS 的问题。

如果发生这样的事情

var spiedMethod = (...args) => {
  console.log('spy!');
  return object.method(...args);
};

这会创建一个新函数,它不会取代原来的 object 方法。如果调用 object.method,则不会调用 spiedMethod - 将调用原始方法。要在 object 上替换它,应修改对象:

object.method = spiedMethod;

这正是诗乃间谍所做的。

let spy = sinon.spy(myFunc);

returns sinon.spy(myFunc) 提供功能的间谍。为了监视调用,应该调用 spy 而不是 myFunc

除非完成这样的事情

helpers.someHelper = sinon.spy(helpers.someHelper);

Sinon 无法将 spy 函数与间谍方法关联起来。这就是

sinon.spy(helpers, 'someHelper')

前来救援。它本质上是做 helpers.someHelper = spy 但也在内部保存了原来的方法,所以当 helpers.someHelper.restore() 被调用时它可以做 helpers.someHelper = someHelperOriginal