firebase-admin-node icon indicating copy to clipboard operation
firebase-admin-node copied to clipboard

Using jest.useFakeTimers() in a test causes firebase-admin/firestore promises to no longer resolve

Open curtis-jotson opened this issue 1 year ago • 8 comments

[READ] Step 1: Are you in the right place?

  • For issues related to the code in this repository file a Github issue.
  • If the issue pertains to Cloud Firestore, read the instructions in the "Firestore issue" template.
  • For general technical questions, post a question on StackOverflow with the firebase tag.
  • For general Firebase discussion, use the firebase-talk google group.
  • For help troubleshooting your application that does not fall under one of the above categories, reach out to the personalized Firebase support channel.

[REQUIRED] Step 2: Describe your environment

  • Operating System version: MacOS 13.5
  • Firebase SDK version: ^11.9.0
  • Firebase Product: firebase-admin/firestore
  • Node.js version: 18.17.0
  • NPM version: 9.8.1

[REQUIRED] Step 3: Describe the problem

Steps to reproduce:

Set up a jest test against firebase-admin/firestore running against a local emulator. If faking out date and timers with jest.useFakeTimers(), a very common way of date/time mocking, suddenly Firebase promises are no longer resolving. Date mocking and faking is very common in the TDD world and the data access library should continue to respond even if "time" isn't moving.

Relevant Code:

  1. Set up a new NPM project and install jest and firebase-admin
  2. Start a Firestore emulator, you can use a different project for this, I didn't include it for simplicity (you could also use an online instance)
  3. Change the PROJECT_ID in the following test file to the projectId the emulator is running for
  4. Run the test with jest, it should pass and add a test document to a test collection
  5. Uncomment the jest.UseFakeTimers() call in beforeAll
  6. Run the test again, it will time-out
process.env.FIRESTORE_EMULATOR_HOST = "127.0.0.1:8080"

const admin = require('firebase-admin')
const { getFirestore } = require('firebase-admin/firestore')

admin.initializeApp({ projectId: "PROJECT_ID" })

const db = getFirestore()

describe('', () => {
    beforeAll(() => {
        // jest.useFakeTimers()
    })

    afterAll(() => {
        jest.useRealTimers()
    })

    test('', async () => {
        await db.collection('test').add({ test: "I am a test document" })
    })
})

curtis-jotson avatar Aug 15 '23 17:08 curtis-jotson

I couldn't figure out how to label this issue, so I've labeled it for a human to triage. Hang tight.

google-oss-bot avatar Aug 15 '23 17:08 google-oss-bot

Hi @curtis-jotson , can you explain more which part of the code experiences time out? For example,

afterAll(() => {
    // put some logs here to see if the line reaches
    jest.useRealTimers()
})

Or

test('', async () => {
    await db.collection('test').add({ test: "I am a test document" })
    // put some logs here to see if the line reaches
})

cherylEnkidu avatar Aug 15 '23 22:08 cherylEnkidu

Hello @cherylEnkidu, sorry for the delay.

This part experiences the timeout:

test('', async () => {
    await db.collection('test').add({ test: "I am a test document" })
    // put some logs here to see if the line reaches
})

when this part is uncommented:

    beforeAll(() => {
        // jest.useFakeTimers()
    })

curtis-jotson avatar Aug 17 '23 16:08 curtis-jotson

Hi @curtis-jotson , it could be possible that faking time changes some underlining javascript and unfortunately we can only guaranteeing the performance on production environment.

cherylEnkidu avatar Aug 21 '23 18:08 cherylEnkidu

It does this when connected to the online environment as well, not just the emulators. It's an issue with library halting data access when "time" isn't moving. No other data access library I've used exhibits this behaviour and it's stopping me from being able to TDD my code effectively.

curtis-jotson avatar Aug 22 '23 20:08 curtis-jotson

Sorry for the confusion here. The team can only guarantee the performance on the production environment which including using the real time. It is possible changing the system time fails some of the security checks in the backend.

cherylEnkidu avatar Aug 22 '23 20:08 cherylEnkidu

I encountered the same problem as this one. I only wanted to mock new Date(), so by providing everything except for Date, which is mentioned at https://jestjs.io/ja/docs/jest-object#jestusefaketimersfaketimersconfig, to the doNotFake option, the test started working

jest.useFakeTimers({
      doNotFake: [
        "hrtime",
        "nextTick",
        "performance",
        "queueMicrotask",
        "requestAnimationFrame",
        "cancelAnimationFrame",
        "requestIdleCallback",
        "cancelIdleCallback",
        "setImmediate",
        "clearImmediate",
        "setInterval",
        "clearInterval",
        "setTimeout",
        "clearTimeout",
      ],
      now: new Date("2022/01/01 0:00:00"),
    });

maemae-dev avatar Oct 18 '23 05:10 maemae-dev

I encountered the same problem as this one. I only wanted to mock new Date(), so by providing everything except for Date, which is mentioned at https://jestjs.io/ja/docs/jest-object#jestusefaketimersfaketimersconfig, to the doNotFake option, the test started working

jest.useFakeTimers({
      doNotFake: [
        "hrtime",
        "nextTick",
        "performance",
        "queueMicrotask",
        "requestAnimationFrame",
        "cancelAnimationFrame",
        "requestIdleCallback",
        "cancelIdleCallback",
        "setImmediate",
        "clearImmediate",
        "setInterval",
        "clearInterval",
        "setTimeout",
        "clearTimeout",
      ],
      now: new Date("2022/01/01 0:00:00"),
    });

Woah thanks! I've been struggling with this for a long time!! I had to mock out Date.now() as a workaround previously.

vojdan avatar Apr 12 '24 02:04 vojdan