Javascript 与使用sinon、enyzme和ES6导入模拟无状态React组件帮助器函数混淆

Javascript 与使用sinon、enyzme和ES6导入模拟无状态React组件帮助器函数混淆,javascript,testing,reactjs,ecmascript-6,sinon,Javascript,Testing,Reactjs,Ecmascript 6,Sinon,我目前正在尝试对React组件进行单元测试,但在模拟函数和辅助函数方面遇到了一些困惑。模块看起来像这样 export const someHelper = () => { return ( <div></div> ) } const MyComponent = () => { return ( <span> {someHelper()} </span> ) } export default My

我目前正在尝试对React组件进行单元测试,但在模拟函数和辅助函数方面遇到了一些困惑。模块看起来像这样

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

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

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

export const helpers = {
  someHelper () {
    ...
  }
}
const MyComponent = () => {
   ...
   {helpers.someHelper()}
   ...
}
describe(...{
  it(...{
    let spy = sinon.spy(helpers, 'someHelper')
     ...
  })
})
现在测试通过了

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

为什么我必须将
someHelper
附加到
helpers
对象才能进行此测试

Sinon必须能够用spy/stub包装版本的函数替换对现有函数的引用,并且只有当引用存储在对象中时,它才能这样做(
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()

为什么我必须使用最后一个
spy(object,'myfunc')
方法

这同样与用包装版本的函数替换函数有关

…当sinon文档显示
spy(myFunc)
选项时

我发现这个习语通常用处不大。正如您所想,它不会监视对
myFunc
的所有调用,除非这些调用是对
spy()的结果spy对象进行的

例如:

let callback = (err, result) => { ... }
...
let spy = sinon.spy(callback);
someFuncToTest(spy);
expect(spy.callCount).to.equal(1);
因此,不直接传递回调函数,而是传递spy

为什么我必须将
someHelper
附加到
helpers
对象才能进行此测试

Sinon必须能够用spy/stub包装版本的函数替换对现有函数的引用,并且只有当引用存储在对象中时,它才能这样做(
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()

为什么我必须使用最后一个
spy(object,'myfunc')
方法

这同样与用包装版本的函数替换函数有关

…当sinon文档显示
spy(myFunc)
选项时

我发现这个习语通常用处不大。正如您所想,它不会监视对
myFunc
的所有调用,除非这些调用是对
spy()的结果spy对象进行的

例如:

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

因此,不直接传递回调函数,而是传递spy。

问题不在于React或Sinon,而在于JS

如果发生这样的事情

var spiedMethod = (...args) => {
  console.log('spy!');
  return object.method(...args);
};
这将创建一个新函数,该函数不会替换原始的
对象
方法。如果调用了
object.method
,则不会调用
spiedMethod
——将调用原始方法。要在
对象
上替换它,应修改该对象:

object.method = spiedMethod;
这正是Sinon间谍所做的

let spy = sinon.spy(myFunc);
返回所提供函数的间谍。为了监视调用,应该调用
spy
,而不是
myFunc

除非这样做

helpers.someHelper = sinon.spy(helpers.someHelper);
Sinon无法将
spy
函数与spied方法相关联。这就是

sinon.spy(helpers, 'someHelper')

来营救。它本质上是执行
helpers.someHelper=spy
,但也在内部保存原始方法,因此当调用
helpers.someHelper.restore()
时,它可以执行
helpers.someHelper=someHelper.restore()。

问题不在于React或Sinon,而在于JS

如果发生这样的事情

var spiedMethod = (...args) => {
  console.log('spy!');
  return object.method(...args);
};
这将创建一个新函数,该函数不会替换原始的
对象
方法。如果调用了
object.method
,则不会调用
spiedMethod
——将调用原始方法。要在
对象
上替换它,应修改该对象:

object.method = spiedMethod;
这正是Sinon间谍所做的

let spy = sinon.spy(myFunc);
返回所提供函数的间谍。为了监视调用,应该调用
spy
,而不是
myFunc

除非这样做

helpers.someHelper = sinon.spy(helpers.someHelper);
Sinon无法将
spy
函数与spied方法相关联。这就是

sinon.spy(helpers, 'someHelper')

来营救。它基本上执行
helpers.someHelper=spy
操作,但也在内部保存原始方法,因此当调用
helpers.someHelper.restore()
时,它可以执行
helpers.someHelper=someHelper-original

回答得好,谢谢!那么,是否真的没有办法存根一个不是通过某个对象(即helpers)直接调用的函数?@Weston不是通过Sinon直接调用的,但可能能够帮助您解决问题(前者可能不会太多,因为它不适用于Babel)。回答得很好,谢谢!那么,是否真的没有办法存根一个直接调用的函数,而不是通过某个对象(即helpers)?@Weston没有直接使用Sinon,但可能能够帮助您解决问题(前者可能不会太多,因为它不适用于Babel)。