为什么 jest mockResolvedValueOnce 多次调用 returns 相同的值?

Why does jest mockResolvedValueOnce called multiple times returns the same value?

我有一个 class 方法,我多次触发 @google-cloud/firestore。我想多次通过相同的 .get() 方法模拟调用。 多次使用具有与 return 不同值的 mockResolvedValueOnce,第二个值将被忽略。

    jest.doMock('@google-cloud/firestore', () => class {
      collection () {
        return {
          get: jest.fn().mockResolvedValue({
            docs: []
          }),
          doc: () => {
            return {
              set: jest.fn(),
              get: jest.fn().mockResolvedValueOnce({})
            }
          },
          limit: () => {
            return {
              get: jest.fn().mockResolvedValue({ empty: true })
            }
          },
          onSnapshot: jest.fn(),
          select: () => {
            return {
              get: jest.fn() // <------------ MULTIPLE CALLS CHAINED BELOW
                .mockResolvedValueOnce({
                  size: 1
                }).mockResolvedValueOnce({
                  size: 2
                })
            }
          }
        }
      }
    })

当我 console.log(snapshot.size) 时,它 return 向我两次调用相同的值“1”。

if (isEmptyModels || isStatsEmptyModels) {
  // ...
  console.log(' [STATS][MODELS] - Fulfilling the counters')
  await Database.collection('models').select('id').get().then(snapshot => {
    console.log(snapshot.size) // <--------- 1st call
    this.fields.models.count = snapshot.size
    this.fields.models.linked = snapshot.size
  })
  // ...
}

if (isEmptyProducts1P || isStatsEmptyProducts1P) {
  // ...
  console.log(' [STATS][PRODUCTS1P] - Fulfilling the counters')
  await Database.collection('products1P').select('isMaintained').get().then(snapshot => {
    console.log(snapshot.size) // <--------- 2nd call
    snapshot.forEach(doc => {
      if (doc.data().isMaintained) {
        // ...
      }
    })
    // ...
  })
  // ...
}

为什么会这样,哪里做错了?

错误信息是:

  console.log
     [STATS][MODELS] - Fulfilling the counters

      at Statistics.fulfillProductsCount (app/services/statistics/index.js:95:15)

  console.log
    1

      at app/services/statistics/index.js:97:17

  console.log
     [STATS][PRODUCTS1P] - Fulfilling the counters

      at Statistics.fulfillProductsCount (app/services/statistics/index.js:106:15)

  console.log
    1

      at app/services/statistics/index.js:108:17


    TypeError: snapshot.forEach is not a function

      117 |       await Database.collection('products1P').select('isMaintained').get().then(snapshot => {
      118 |         console.log(snapshot.size)
    > 119 |         snapshot.forEach(doc => {
          |                  ^
      120 |           if (doc.data().isMaintained) {
      121 |             this.fields.products1P.maintained += 1
      122 |           } else {

      at app/services/statistics/index.js:119:18

发生这种情况是因为每次调用 Database.collection() 时,它都会创建一个新对象,并且作为一个新对象,这是第一次调用其属性。它对集合中的其他函数也有效。

我的意思是 Database.collection 是一个函数,return 是一个包含其他函数的对象,return 对象包含模拟的属性。通过这种方式进行模拟,您将永远无法使用 mock...ValueOnce。但是,我看到有两种方法可以“绕过”这个问题:

1 - 简短但矛盾的方法

您可以使用 .mockReturnThis() 来避免进入深度模拟 objects/functions,但在处理具有多次相同方法名称的“fat”类 时可能会很快发生冲突.在模拟可链接方法时也很有用(例如:ORM 查询与 .find().filter().sort()...)。

jest.doMock('@google-cloud/firestore', () => class {
  collection = jest.fn().mockReturnThis();
  select = jest.fn().mockReturnThis();
  get = jest.fn().mockResolvedValueOnce({ size: 1 }).mockResolvedValueOnce({ size: 2 });
})

2 - 漫长但可行的方法

模拟整个收集方法一次,而不是只模拟一次 collection().select().get()

Database.collection.prototype.mockReturnValueOnce({
  select: () => {
    get: () => ({ size: 1 })
  }
}).mockReturnValueOnce({
  select: () => {
    get: () => ({ size: 2 })
  }
})

--> 您将需要访问模拟的 Class 并模拟原型的方法“集合”(collection = jest.fn())。