0

While using the Google Cloud's Firestore Emulator, I'm calling the following method:

global.db.runTransaction(async () => 100)

This dummy call works when executed using node, but fails when executed using jest inside a test function. When running it from Jest, the runTransaction method throws:

Error: You must return a Promise in your transaction()-callback.

referencing node_modules/@google-cloud/firestore/build/src/transaction.js:362:27

That file has the following snippet of code, which is causing the failure:

async runTransaction(updateFunction, options) {
  // ...
  const promise = updateFunction(this);
  if (!(promise instanceof Promise)) {
    throw new Error('You must return a Promise in your transaction()-callback.');
  }
  // ...
}

In other words, when running in Jest, the library considers async () => 100 function not to return a promise.

I changed the library code to add some debug messages:

async runTransaction(updateFunction, options) {
  // ...
  const promise = updateFunction(this);
  if (!(promise instanceof Promise)) {
    // *** Added these debug messages ***
    console.log(updateFunction);
    console.log(promise);
    console.log(promise instanceof Promise);

    throw new Error('You must return a Promise in your transaction()-callback.');
  }
  // ...
}

And I am getting the following:

[AsyncFunction (anonymous)]
Promise { 100 }
false

To complicate matters, this test passes as expected within jest:

test('promise', () => {
  const fn = async () => 100;
  const res = fn();
  expect(res instanceof Promise).toEqual(true);
}

I'm a bit at a loss here as to why executing this library code with Jest fails compared to Node, while executing a similar statement with Jest in the code snippet just above succeeds.

Any suggestions on how to approach this head-scratcher?

EDIT

Initializing a local db variable instead of global.db solves the issue.

Originally, I had the global.db set in jest.config.js like so:

module.exports = async () => {
  const Firestore = require("@google-cloud/firestore");
  global.db = new Firestore({ projectId: process.env.GOOGLE_CLOUD_PROJECT });

  return {}
}

The application code works by relying on global.db that is set through an initialization file, and I wanted to mimic this for the tests.

I guess the question is, then, how should one go about initializing a global db variable that can be used in all tests?

ANSWER

Ended up creating a setup.js file and loaded it with setupFilesAfterEnv per the docs. Content:

const Firestore = require("@google-cloud/firestore/build/src");
global.db = new Firestore({ projectId: process.env.GOOGLE_CLOUD_PROJECT });

Now things are working properly. Other solutions, such as using the globals entry in the object exported in jest.config.js (not shown in code snippets above) didn't work as per the documentation the values must be JSON-serializable, which is not the case for Firestore.

AmitA
  • 3,239
  • 1
  • 22
  • 31
  • Have you tried db.runTransaction(async () => Promise.resolve(100)) – Kostas Minaidis Jul 16 '22 at 13:47
  • 1
    Related: https://stackoverflow.com/questions/44043619/why-does-jest-fail-the-test-when-checking-for-a-valid-instance-of-promise https://stackoverflow.com/questions/27746304/how-to-check-if-an-object-is-a-promise – SuperStormer Jul 16 '22 at 13:48
  • 1
    Does this answer your question? [Jest's expect(value).toBeInstanceOf(Class) fails for expect(util.promisify(...)()).toBeInstanceOf(Promise) and others](https://stackoverflow.com/questions/58029714/jests-expectvalue-tobeinstanceofclass-fails-for-expectutil-promisify) – SuperStormer Jul 16 '22 at 13:48
  • @KostasMinaidis tried both `async () => Promise.resolve(100)` and `() => new Promise((res, rej) => res(10))` and getting same behavior. – AmitA Jul 16 '22 at 13:51
  • @SuperStormer OP is not on his own testing for a `Promise` (with Jest). It's Googles Firestore that fails the test. – Andreas Jul 16 '22 at 13:54
  • @SuperStormer indeed @Andreas is correct - the failure happens inside library code, not a Jest `expect` function. I cannot alter the way that the library code performs the Promise type check. Nevertheless, `expect((async () => 100)() instanceof Promise).toEqual(true)` works, so I'm at a loss as to why a similar statement inside the library code fails, even with Jest. – AmitA Jul 16 '22 at 15:46
  • @Andreas The problem is the same: jest is messing with the globals. – Bergi Jul 17 '22 at 15:04

0 Answers0