[api] migrate testing framework (Jest > Vitest)#3555
Conversation
freemvmt
left a comment
There was a problem hiding this comment.
Some comments for easier digestion!
| post: jest.fn(), | ||
| })); | ||
| const mockAxios = Axios as jest.Mocked<typeof Axios>; | ||
| describe("Creation of scheduled event", () => { |
There was a problem hiding this comment.
Wrapped tests which I adapted in a describe if it didn't already exist 🫡
| })); | ||
| const mockAxios = Axios as jest.Mocked<typeof Axios>; | ||
| describe("Creation of scheduled event", () => { | ||
| vi.mock("axios", async (importOriginal) => { |
There was a problem hiding this comment.
vitest has this importOriginal argument which is passed to the factory constructor in a mock call - we can use this to import the actual module (replacing jest.requireActual), as per the docs.
Could also have used vi.importActual here, but using the arg seemed neater.
| vi.mock("axios", async (importOriginal) => { | ||
| const actualAxios = await importOriginal<typeof import("axios")>(); | ||
| return { | ||
| default: { |
There was a problem hiding this comment.
Vitest does not assume, like jest, that a returned object is the default export of given module, so we have to be explicit about it, as per the docs.
|
|
||
| describe("CSV data admin endpoint", () => { | ||
| afterEach(() => jest.clearAllMocks()); | ||
| afterEach(() => { |
There was a problem hiding this comment.
hooks like afterEach have to return null or undefined, so have remove implicit return, as per docs
| // Please comment in and run locally if making changes to /roads functionality | ||
| describe.skip("fetching classified roads data from OS Features API for any local authority", () => { | ||
| jest.setTimeout(10000); | ||
| vi.setConfig({ testTimeout: 1000 }); |
| // Assert | ||
| expect(result).toHaveProperty(key); | ||
| layersToRollup.forEach((layer) => { | ||
| // Jest can handle paths using dot notation, so keys with a dot need to be wrapped in [] |
There was a problem hiding this comment.
I assume this was supposed to say 'cannot handle...' ?
There was a problem hiding this comment.
I actually think "can" was correct here - the key here would have been something like a.b.c which Jest would have tried to default to a nested path, as opposed to the object in question which is likely { "a.b.c": "x" }
Either way, it's a good call to remove this comment - it looks like vitest handles this more gracefully - the test actually passes with or without the wrapping [] on line 154.
| import { createScheduledEvent } from "../../../../lib/hasura/metadata/index.js"; | ||
| import { queryMock } from "../../../../tests/graphqlQueryMock.js"; | ||
| import { flowWithInviteToPay } from "../../../../tests/mocks/inviteToPayData.js"; | ||
| import { MockedFunction } from "vitest"; |
There was a problem hiding this comment.
no vi namespace so we have to explicitly import types like this one, as per docs
| }; | ||
|
|
||
| const mockSend = jest.fn(); | ||
| jest.mock<typeof SlackNotify>("slack-notify", () => |
There was a problem hiding this comment.
tsc/vitest seem smart enough not to need the type declaration anymore 🤔
There was a problem hiding this comment.
Ah that's awesome because this step always felt awkward and brittle in jest imho 🙌
| "test:watch": "TZ=Europe/London NODE_OPTIONS='$NODE_OPTIONS --experimental-vm-modules' jest --coverage=false --watch", | ||
| "test": "TZ=Europe/London vitest run", | ||
| "test:coverage": "TZ=Europe/London vitest run --coverage && open coverage/lcov-report/index.html", | ||
| "test:watch": "TZ=Europe/London vitest --ui --coverage", |
|
Removed vultr server and associated DNS entries |
DafyddLlyr
left a comment
There was a problem hiding this comment.
Fantastic PR - thanks so much for the documentation links and explanations, super helpful.
| "eslint:recommended", | ||
| "plugin:@typescript-eslint/recommended", | ||
| "prettier", | ||
| "plugin:jest/recommended" |
There was a problem hiding this comment.
Super happy for this to be a follow up PR - it might be worth looking at something like https://www.npmjs.com/package/eslint-plugin-vitest to replace this?
| // Assert | ||
| expect(result).toHaveProperty(key); | ||
| layersToRollup.forEach((layer) => { | ||
| // Jest can handle paths using dot notation, so keys with a dot need to be wrapped in [] |
There was a problem hiding this comment.
I actually think "can" was correct here - the key here would have been something like a.b.c which Jest would have tried to default to a nested path, as opposed to the object in question which is likely { "a.b.c": "x" }
Either way, it's a good call to remove this comment - it looks like vitest handles this more gracefully - the test actually passes with or without the wrapping [] on line 154.
There was a problem hiding this comment.
Really appreciate the comments in this config file 💪
| functions: 55, | ||
| // TODO: could add autoUpdate flag here so that function coverage is only allowed to increase |
There was a problem hiding this comment.
Let's do this and leave this better than we found it 👍
a94d651 to
c870585
Compare
|
Merged in @freemvmt 's absence. I'll pick up the two (very small) tasks suggested above in the comments -
|

This PR is motivated by Jest's failure to handle ESM modules sufficiently well, which was a blocker on the Microsoft SSO work (ticket). In the end we decided to migrate away from Jest, with Vitest being the obvious alternative, because a quick experiment with it showed promise (see this comment).
Ticket: https://trello.com/c/N1egz8at
So, this PR does the following:
vitest.config.ts, setup files,package.jsonscripts etc)api.planx.ukto usevitestinstead ofjest- this was the most laborious step and I will document some of the thornier aspects in my own review shortly (Vitest's dedicated migration guide was very useful)A note on coverage
The
text-summarycoverage results are slightly different in this branch compared withmain.main (jest)
api-migrate-jest-vitest (vitest)
I haven't seriously investigated the cause of this discrepancy because it doesn't seem a substantial change. My instinct is that it's due to the default exclusions of each framework - e.g. as far as I can see, vitest has a long list of exclusions, whereas jest just ignores
node_modules(or perhaps also excludes the istanbul defaults). But it might be some other implementation detail.If we wanted to try and create a culture of always-increasing coverage, we could adopt the
autoUpdateflag, as per my comment invitest.config.ts. But for now I've stuck with the singular minimum coverage threshold of 55% of functions.