【问题标题】:How to spy on a sub-function of a named export in ES6如何在 ES6 中监视命名导出的子函数
【发布时间】:2016-09-17 04:02:39
【问题描述】:

我想监视作为命名导出导出的子函数,但是,我们似乎无法监视它。

假设我在operations.js 中有两个名为addmultiply 的函数,并将它们导出为命名导出:

const add = (a, b) => {
  return a + b
}

const multiply = (a, b) => {
  let result = 0

  for (let i = 0; i < b; i++) {
    result = add(a, result)
  }
  return result
}

export { add, multiply }

而测试文件使用sinon-chai来试图窥探​​add函数:

import chai, { expect } from 'chai'
import sinon from 'sinon'
import sinonChai from 'sinon-chai'
import * as operations from './operations'

chai.use(sinonChai)

describe('multiply', () => {
  it('should call add correctly', () => {
      sinon.spy(operations, 'add')

      operations.multiply(10, 5)
      expect(operations.add).to.have.been.callCount(5)

      operations.add.restore()
  })
})

结果是

AssertionError: expected add to have been called exactly 5 times, but it was called 0 times

但是,如果我像下面这样直接调用operations.add,它通过了测试:

describe('multiply', () => {
  it('should call add correctly', () => {
      sinon.spy(operations, 'add')

      operations.add(0, 5)
      operations.add(0, 5)
      operations.add(0, 5)
      operations.add(0, 5)
      operations.add(0, 5)
      expect(operations.add).to.have.been.callCount(5)

      operations.add.restore()
  })
})

似乎 sinon-spy 为 operations.add 创建了一个新引用,但 multiply 函数仍然使用已绑定的旧引用。

如果这些函数被命名为exports,那么窥探add函数的add函数的正确方法是什么?

一般来说,如何监视一个被测试的父函数的子函数,它们都被命名为exports?

[更新]

multiply 函数只是一个例子。我的主要观点是测试父函数是否调用子函数。但是,我不希望该测试依赖于子功能的实现。所以,我只想窥探子函数是否被调用。你可以想象multiply 函数是registerMember 函数,add 函数是sendEmail 函数。 (这两个函数都被命名为导出。)

【问题讨论】:

  • 从经验来看,像这样的测试最终会变得非常脆弱。 multiply 的返回值是重要的,而不是它是如何实现的。最好专注于测试。
  • export { add, multiply } 类似于 export { add : add, multiply : multiply }。所以导出的对象包含对原始方法的引用,sinon.spy() 替换了引用而不是原始方法(这是multiply() 使用的)。
  • @loganfsmyth 在某些情况下,一个函数不返回任何值,但它会调用其他函数来做某事。因此,我们想测试它是否调用了其他函数。
  • @robertklep 我想是的。所以,我想知道是否可以监视它。 (不一定是sinon。)
  • @Supasate 我不认为你可以监视它,因为multiply() 没有使用导出的引用。

标签: javascript unit-testing ecmascript-6 spy sinon-chai


【解决方案1】:

我有自己的问题的解决方法。

当前,multiply() 函数与 add() 函数紧密耦合。这使得测试变得困难,尤其是在导出的函数获得新引用时。

因此,要监视子函数调用,我们可以将子函数传递给父函数。是的,它是dependency injection

因此,在operations.js 中,我们将addFn 注入multiply() 并按如下方式使用:

const add = (a, b) => {
  return a + b
}

const multiply = (a, b, addFn) => {
  let result = 0

  for (let i = 0; i < b; i++) {
    result = addFn(a, result)
  }
  return result
}

export { add, multiply }

那么,在测试中,我们可以像这样窥探add()函数:

describe('multiply', () => {
  it('should call add correctly', () => {
      sinon.spy(operations, 'add')

      operations.multiply(10, 5, operations.add)
      expect(operations.add).to.have.been.callCount(5)

      operations.add.restore()
  })
})

现在,它的作用是测试子函数是否被正确调用。

(注意:缺点是我们需要更改multiply()函数)

【讨论】:

  • 您出于测试目的更改了实现。这是不好的做法。我们为代码编写测试,而不是为测试编写代码。
  • @vp_arth 更改测试目的的实现是可以的。您应该使您的代码可测试。如果您无法测试您的代码,那么这就是遗留代码,这很糟糕。我认为这里的问题是 OP 正在检查 add 是否被多次调用,这通常并不重要。您应该测试合同而不是实施细节。
  • 我认为您的主要问题是您正在测试调用add 的次数。因此,您总是假设multiply 将使用add。这是紧耦合和糟糕的测试 IMO。真的,你想使用间谍或模拟的唯一时间是当你想返回某些结果或测试回调(这是接口的一部分)时。乘以调用 add X 次是实现细节。
  • 现在不完全是multiply 函数。只需传入(a,b)=&gt;a*b,它就会变成power 函数:)
  • 其实重点是测试父函数正确调用子函数,不想依赖子函数的实现来隔离测试。我只是将multiply 函数作为一个例子(我对此不好)。
猜你喜欢
  • 2016-03-10
  • 2020-07-25
  • 2015-12-29
  • 1970-01-01
  • 2022-01-14
  • 1970-01-01
  • 1970-01-01
  • 2016-04-07
  • 1970-01-01
相关资源
最近更新 更多