How to spy on a third party ES6 export in Vitest

erikpuk

Erik Pukinskis

Posted on June 26, 2023

How to spy on a third party ES6 export in Vitest

This was a bit of a tricky one, and the solution from the Vitest docs didn't work for me.

Those docs suggested that I could do something like:



import * as Firestore from "firebase/firestore"
import { vi } from "vitest"

const onSnapshot = vi.spyOn(Firestore, "onSnapshot")
...
expect(onSnapshot).toHaveBeenCalled()


Enter fullscreen mode Exit fullscreen mode

but that didn't work, Vitest complains that it can't redefine that propery:



TypeError: Cannot redefine property: onSnapshot
 ❯ lib/useDoc.test.ts:12:4
     10| import type { Repo } from "~/test/helpers/factory"
     11| 
     12| vi.spyOn(Firestore, "onSnapshot")


Enter fullscreen mode Exit fullscreen mode

The solution

The answer wasn't too bad, but there are actually three steps:

1) Mock the import
2) Import the module in your test
3) Create the spy

Let's walk through each of those.

1) Mock the import

At the top level of my test file you need to mock the import using vi.mock. In the factory you feed to vi.mock you will replace the function you want to spy on with with a vi.fn:



type FakeFirestore = { onSnapshot(this: void): void }

vi.mock("firebase/firestore", async (getModule) => {
  const original: FakeFirestore = await getModule()

  return {
    ...original,
    onSnapshot: vi.fn().mockImplementation(original.onSnapshot),
  }
})


Enter fullscreen mode Exit fullscreen mode

Note that very useful importOriginal function which lets me keep the Firestore module fully functional. This is because vi.mock is really persnickety about referencing outside variables.

That factory function needs to be 100% self contained. Therefore you have to...

2) Import the module in your test

In order to have something to spy on, you'll need to import that same module directly in your test. This should be the mock object that you returned in your factory above, with the vi.fn where you stuck it. That'll give us something to spy on...



import * as Firestore from "firebase/firestore"


Enter fullscreen mode Exit fullscreen mode

3) Create the spy

Finally, we can create the spy, and place our expectations on it:



import { test, vi } from "vitest"

test("calls onSnapshot", () => {
  const onSnapshot = vi.spyOn(Firestore, "onSnapshot")

  /* trigger the onSnapshot call here in my code */

  expect(onSnapshot).toHaveBeenCalled()
})


Enter fullscreen mode Exit fullscreen mode

And that's it! My test passes.

Check out the complete source code here.

Follow me on Twitter if you want to see more Vite, Vitest, and Firestore tips!

Image description

💖 💪 🙅 🚩
erikpuk
Erik Pukinskis

Posted on June 26, 2023

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related