【问题标题】:Why does jest.fn().mockImplementation not see the reset variable when it's declared in the beforeEach scope?为什么 jest.fn().mockImplementation 在 beforeEach 范围内声明时看不到重置变量?
【发布时间】:2021-02-12 10:50:45
【问题描述】:

给定一个正在测试的模块sut.js

const { dependencyFunc } = require('./dep')

module.exports = () => {
  return dependencyFunc()
}

依赖dep.js

module.exports = {
  dependencyFunc: () => 'we have hit the dependency'
}

还有一些测试:

describe('mocking in beforeEach', () => {
  let sut
  let describeScope

  beforeEach(() => {
    let beforeEachScope = false
    describeScope = false

    console.log('running before each', { beforeEachScope, describeScope })
    jest.setMock('./dep', {
      dependencyFunc: jest.fn().mockImplementation(() => {
        const returnable = { beforeEachScope, describeScope }
        beforeEachScope = true
        describeScope = true
        return returnable
      })
    })
    sut = require('./sut')
  })

  it('first test', () => {
    console.log(sut())
  })

  it('second test', () => {
    console.log(sut())
  })
})

我得到以下输出:

me$ yarn test test.js
yarn run v1.22.5
$ jest test.js
 PASS  ./test.js
  mocking in beforeEach
    ✓ first test (17 ms)
    ✓ second test (2 ms)

  console.log
    running before each { beforeEachScope: false, describeScope: false }

      at Object.<anonymous> (test.js:9:13)

  console.log
    { beforeEachScope: false, describeScope: false }

      at Object.<anonymous> (test.js:22:13)

  console.log
    running before each { beforeEachScope: false, describeScope: false }

      at Object.<anonymous> (test.js:9:13)

  console.log
    { beforeEachScope: true, describeScope: false }

      at Object.<anonymous> (test.js:26:13)

Test Suites: 1 passed, 1 total
Tests:       2 passed, 2 total
Snapshots:   0 total
Time:        1.248 s, estimated 2 s
Ran all test suites matching /test.js/i.
✨  Done in 3.56s.

我希望这两个测试的输出都是{ beforeEachScope: false, describeScope: false }。即,我希望beforeEachScope 和describeScope 变量都被重置为false,无论它们是在beforeEach 范围内还是在describe 范围内声明的。在我的真实测试中,我认为将它放在beforeEach 范围内会更干净,因为其他地方不需要它。这是怎么回事? Jest 使用的范围是什么?

【问题讨论】:

    标签: javascript scope jestjs


    【解决方案1】:

    我看起来像setMock 只考虑给定模块的第一个模拟,并且不会在第二次调用时覆盖它。或者更确切地说,我相信是 require 进行缓存 - 开玩笑地在运行整个测试套件之前只清空模块缓存一次(预计 the imports will be at the top of the suite,在声明要模拟哪些模块之后)。

    然后,您的模拟实现对来自第一个 beforeEach 调用的 beforeEachScope 有一个闭包。

    您可能想知道,为什么describeScope 似乎也没有闭包?事实上,确实如此,您的代码中可能令人困惑的是beforeEach 确实运行describeScope = false,它总是在它被记录到任何地方之前将其重置为false。如果您删除该语句,而仅在 describe 范围内初始化 let describeScope = false,您将看到它在第一次 sut() 调用之后也将更改为 true。

    如果我们手动解析范围并从执行中删除所有开玩笑的包装器,会发生以下情况:

    let sut
    let describeScope
    
    // first test, beforeEach:
    let beforeEachScope1 = false
    describeScope = false
    
    console.log('running before each 1', { beforeEachScope1, describeScope }) // false, false as expected
    jest.setMock('./dep', {
      dependencyFunc(n) {
        console.log('sut call '+n, { beforeEachScope1, describeScope });
        beforeEachScope1 = true
        describeScope = true
      })
    })
    sut = require('./sut') // will call the function we just created
    
    // first test
    sut(1) // still logs false, false
    
    // second test, beforeEach:
    let beforeEachScope2 = false // a new variable
    describeScope = false // reset from true to false, you shouldn't do this
    
    console.log('running before each 2', { beforeEachScope2, describeScope }) // logs false, false
    jest.setMock('./dep', {
      dependencyFunc(n) {
        // this function is never called
      })
    })
    sut = require('./sut') // a no-op, sut doesn't change (still calling the first mock)
    
    // second test:
    sut(2) // logs true (beforeEachScope1) and false
    

    使用以下内容:

    const dependencyFunc = jest.fn();
    jest.setMock('./dep', {
      dependencyFunc,
    })
    const sut = require('./sut')
    
    describe('mocking in beforeEach', () => {
      let describeScope = false
    
      beforeEach(() => {
        let beforeEachScope = false
    
        console.log('running before each', { beforeEachScope, describeScope })
        dependencyFunc.mockImplementation(() => {
          const returnable = { beforeEachScope, describeScope }
          beforeEachScope = true
          describeScope = true
          return returnable
        })
      })
    
      it('first test', () => {
        console.log(sut())
      })
    
      it('second test', () => {
        console.log(sut())
      })
    })
    

    以下演示了组合的缓存和范围/闭包行为。

    let cachedFunction
    
    let varInGlobalClosure
    
    const run = () => {
      varInGlobalClosure = false
      let varInRunClosure = false
    
      // next line is what jest.mock is doing - caching the function
      cachedFunction = cachedFunction || (() => {
        const returnable = { varInRunClosure, varInGlobalClosure }
        varInRunClosure = true
        varInGlobalClosure = true
        return returnable
      })
    
      return cachedFunction
    }
    
    console.log('first run', run()()) // outputs { varInRunClosure: false, varInGlobalClosure: false }
    console.log('second run', run()()) // outputs { varInRunClosure: true, varInGlobalClosure: false }
    

    这是因为我们在run内部创建了一个新的闭包,当我们第二次调用run时使用了一个新的varInRunClosure,但是缓存的函数仍然使用第一次生成的闭包@987654340 @ran,现在在缓存函数范围之外无法访问。

    【讨论】:

    • 您好,Bergi 感谢您的回复。在我的真实测试中,我正在模拟一个数据库存储库,并且我想在测试之间重置数据 - describeScope = false 和 beforeEachScope = false 是我正在重置数据,这说明了我面临的问题。我不需要在测试期间访问模拟数据存储库的内部。如果变量在 beforeEach 闭包中声明,为什么数据(此处使用 beforeEachScope 和 describeScope 表示)不会重置?
    • 确实如此。 sut = require('./sut') 不会重置为新的模拟模块。您将获得与第一次 require 调用相同的模块,并使用旧数据。
    • 对不起,我没有关注。无论是否需要缓存模块,数据是否都存储在闭包的范围内(beforeEach 或 describe),因为那是变量被初始化的地方?如果不是这种情况,为什么使用 beforeEach 的闭包与使用 describe 的闭包的工作方式不同?
    • 是的,变量存储在您声明它们的作用域中,没有魔法发生。它们的工作方式不同,因为您在 beforeEach 中重置了 describeScope = false 而不是 beforeEachScope。
    • 非常感谢代码示例和持续的帮助 - 现在很清楚了。闭包的工作方式与我的预期略有不同。我已要求对您的答案进行编辑,将事情浓缩为关闭。
    猜你喜欢
    • 1970-01-01
    • 2014-08-11
    • 2019-01-19
    • 1970-01-01
    • 2016-05-26
    • 1970-01-01
    • 1970-01-01
    • 2018-01-06
    • 1970-01-01
    相关资源
    最近更新 更多