You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Jest使用doMock多次模拟同一个get方法解决Firestore测试报错

问题原因
  • 你混淆了两个完全独立的get方法:第一个get()是Firestore文档引用对象的方法,调用后返回DocumentSnapshot快照实例;第二个get('categories')是快照实例本身自带的字段读取方法,你之前的mock只给第一个get()的返回值加了exists属性,没有实现快照实例的get方法,所以触发类型错误。
  • 额外错误:exists是快照实例的只读布尔属性,不是方法,你之前mock成了函数,就算不报类型错误,逻辑判断也会失效。
解决方案

我们可以按业务分支拆分测试用例,用Jest的mockImplementationOnce/mockReturnValueOnce控制不同调用的返回值,同时分层实现Firestore的mock结构即可覆盖所有场景。

修正后的测试代码示例:

// 每个测试前重置模块缓存,避免不同用例的mock互相影响
beforeEach(() => {
  jest.resetModules()
})

test('文档不存在时,产品写入orphans集合', async () => {
  const setSpy = jest.fn().mockResolvedValue(true)
  jest.doMock('@google-cloud/firestore', () => class {
    collection (collectionName) {
      return {
        doc: (docId) => {
          return {
            // 文档引用的get方法,返回不存在的快照
            get: jest.fn().mockResolvedValue({
              exists: false
            }),
            set: setSpy
          }
        }
      }
    }
  })

  const observer = require('./product')
  const testPayload = { model: 'test-model', sku: 'test-sku' }
  await observer('created', testPayload)

  // 验证orphans集合的set方法被正确调用
  expect(setSpy).toHaveBeenCalledTimes(1)
  expect(setSpy).toHaveBeenCalledWith(testPayload)
})

test('文档存在但分类为空时,产品写入orphans集合', async () => {
  const setSpy = jest.fn().mockResolvedValue(true)
  jest.doMock('@google-cloud/firestore', () => class {
    collection (collectionName) {
      return {
        doc: (docId) => {
          return {
            get: jest.fn().mockResolvedValue({
              exists: true,
              // 实现快照的get方法,返回空分类数组
              get: (field) => field === 'categories' ? [] : null
            }),
            set: setSpy
          }
        }
      }
    }
  })

  const observer = require('./product')
  const testPayload = { model: 'test-model', sku: 'test-sku' }
  await observer('created', testPayload)

  expect(setSpy).toHaveBeenCalledTimes(1)
})

// 注意替换为你业务中deleted方法的实际引入路径
test('文档存在且分类不为空时,调用deleted移除orphans记录', async () => {
  const deletedSpy = jest.fn().mockResolvedValue(true)
  jest.mock('./deleted方法的模块路径', () => deletedSpy)
  
  jest.doMock('@google-cloud/firestore', () => class {
    collection (collectionName) {
      return {
        doc: (docId) => {
          return {
            get: jest.fn().mockResolvedValue({
              exists: true,
              get: (field) => field === 'categories' ? ['数码', '手机'] : null
            })
          }
        }
      }
    }
  })

  const observer = require('./product')
  const testPayload = { model: 'test-model', sku: 'test-sku' }
  await observer('created', testPayload)

  expect(deletedSpy).toHaveBeenCalledTimes(1)
  expect(deletedSpy).toHaveBeenCalledWith(testPayload.sku)
})

如果你确实需要在同一个测试用例中模拟同一个方法多次调用返回不同值,可使用mockReturnValueOnce按调用顺序定义返回值:

get: jest.fn()
  .mockResolvedValueOnce({ exists: false }) // 第一次调用返回值
  .mockResolvedValueOnce({ exists: true, get: () => ['cate1'] }) // 第二次调用返回值

内容的提问来源于stack exchange,提问作者aspirinemaga

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.02 21:15:03