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

Viewed 1989

I have a class method where I trigger a @google-cloud/firestore multiple times. I would like to mock the call over the same .get() method multiple times. Using a mockResolvedValueOnce multiple times with different values to return, the 2nd value is ignored.

    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
                })
            }
          }
        }
      }
    })

When I console.log(snapshot.size) it returns me the same value "1" twice for both calls.

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) {
        // ...
      }
    })
    // ...
  })
  // ...
}

Why is that, and what is done wrong here ?

Error message is:

  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
1 Answers

This happens because each time you call Database.collection(), it creates a new object, and as a new object, this is the first time its properties are called. It is also valid for the others functions inside collection.

What I mean is that Database.collection is a function that returns an object that contains other functions that return object that contains properties mocked. By mocking this way, you will never be able to use mock...ValueOnce. But, I see two ways to "bypass" this problem :

1 - The short but conflicting way

You can use .mockReturnThis() to avoid entering into deep mock objects/functions, but in may be quickly conflicting when dealing with "fat" classes that have multiple times the same method names. In can also be helpful when mocking chainable methods (example: ORM queries with .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 - The Long but working way

Mock the whole collection method Once instead of mocking only collection().select().get().

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

--> You will need access to the mocked Class and mock the method "collection" of the prototype (collection = jest.fn()).

Related