如何在Jest中重置手动模拟

时间:2018-03-26 15:07:43

标签: javascript unit-testing mocking jestjs

我有crypto的手动模拟,如下所示:

// __mocks__/crypto.js

const crypto = jest.genMockFromModule('crypto')
const toString: Function = jest.fn(() => {
  return {}.toString()
})
const mockStringable = {toString}
const update: Function = jest.fn(() => mockStringable)
const deciper = {update}
crypto.createDecipheriv = jest.fn(() => deciper)

export default crypto

这基本上是这样测试的:

const crypto = require('crypto')
jest.mock('crypto')

describe('cookie-parser', () => {
  afterEach(() => {
    jest.resetAllMocks()
  })
  describe('decryptCookieValue', () => {
    it('should call the crypto library correctly', () => {
      const result = decryptCookieValue('test-encryption-key', 'test-encrypted-value')
      expect(crypto.pbkdf2Sync).toHaveBeenCalledTimes(2)
      expect(crypto.createDecipheriv).toHaveBeenCalled()
      // more tests, etc, etc, etc
      expect(crypto.createDecipheriv('', '', '').update).toHaveBeenCalled()
      expect(result).toEqual({}.toString())
    })
  })

  ...

但是如果在同一个测试文件中,我测试另一个从decryptCookieValue内调用crypto.createDecipheriv的方法不再返回我的模拟解码。相反,它返回undefined。例如:

describe('cookie-parser', () => {
  afterEach(() => {
    jest.resetAllMocks()
  })
  describe('decryptCookieValue', () => {
    it('should call the crypto library correctly', () => {
      const result = decryptCookieValue('test-encryption-key', 'test-encrypted-value')
      expect(crypto.pbkdf2Sync).toHaveBeenCalledTimes(2)
      expect(crypto.createDecipheriv).toHaveBeenCalled()
      expect(crypto.createDecipheriv('', '', '').update).toHaveBeenCalled()
      expect(result).toEqual({}.toString())
    })
  })
  ...
  ...
  describe('parseAuthenticationCookie', () => {
    it('should create the correct object', () => {

      // parseAuthenticationCookie calls decryptCookieValue internally

      const result = parseAuthenticationCookie('', '') // Fails because internal call to crypto.createDecipheriv stops returning mock decipher.
      expect(result).toEqual({accessToken: null})
    })
  })
})

我认为这是重置手动模拟的一个问题,因为如果我接受后来的测试并将其全部移动到一个文件中并使用相同的周围测试工具,那就可以了。

// new test file
import crypto from 'crypto'
import { parseAuthenticationCookie } from './index'

jest.mock('crypto')

describe('cookie-parser', () => {
  afterEach(() => {
    jest.resetAllMocks()
  })
  describe('parseAuthenticationCookie', () => {
    it('should create the correct object', () => {

      // Works just fine now

      const result = parseAuthenticationCookie('', '')
      expect(result).toEqual({accessToken: null})
    })
  })
})

我的评估是否正确,如果是,我如何在每次测试后重置手动模拟的状态?

2 个答案:

答案 0 :(得分:1)

来自Jest文档: 执行mockFn.mockClear()所做的所有事情,并删除所有模拟的返回值或实现。 参考:https://jestjs.io/docs/en/mock-function-api#mockfnmockreset

在您的示例中,您假设调用resetAllMocks会将您的手动模拟放回原处,而不会。

您的测试在单独的文件中工作的原因是,jest在隔离的每个文件中运行,这很好,因为您只能搞乱同一个文件中的规范。

在您的特定情况下,可能有用的方法是调用jest.clearAllMocks()(因为这将保留实现和返回的值)。 jest config对象上还提供了clearMocks选项(默认为false),如果您想在每次测试中清除所有模拟,这可能会很方便。 希望这对您或其他遇到类似问题的人有所帮助。

奖励技巧(没有完全关联):如果您要模拟其他模块在内部使用的模块,并且在某些特定测试中您想使用其他模拟再次模拟该模块,请确保在该特定测试中再次要求其内部使用模拟模块的模块,否则该模块仍将引用您在import语句旁边指定的模拟。

答案 1 :(得分:-2)

看起来更好的测试方法是:

jest.mock('crypto')

describe('decrypt()', () => {
  afterEach(() => {
    jest.resetAllMocks()
  })
  it('returns value', () => {
    const crypto = require('crypto')
    const encryptedValue = 'encrypted-value'
    const update = jest.fn()
    const pbkdf2SyncResult = 'test result'
    crypto.pbkdf2Sync = jest.fn().mockImplementation(() => {
      return pbkdf2SyncResult
    })
    crypto.createDecipheriv = jest.fn().mockImplementation((format, key, iv) => {
      expect(format).toEqual('aes-256-cbc')
      expect(key).toEqual(pbkdf2SyncResult)
      expect(iv).toEqual(pbkdf2SyncResult)
      return {update}
    })
    decrypt(encryptedValue)
    const inputBuffer = Buffer.from(encryptedValue, 'base64')
    expect(update).toHaveBeenCalledWith(inputBuffer)
  })
})

这样我甚至不需要手动模拟,如果我需要模拟重置,我可以使用mockImplementationOnce