-
Notifications
You must be signed in to change notification settings - Fork 2
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[api] migrate testing framework (Jest > Vitest) #3555
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
@@ -23,7 +23,9 @@ jest.mock("@opensystemslab/planx-core", () => { | |||
}); | |||
|
|||
describe("CSV data admin endpoint", () => { | |||
afterEach(() => jest.clearAllMocks()); | |||
afterEach(() => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
hooks like afterEach
have to return null
or undefined
, so have remove implicit return, as per docs
@@ -18,7 +18,7 @@ it("returns an error if required query param is missing", async () => { | |||
// "Success" test commented out due to reliance on external API calls and fallibility of nocks | |||
// 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 }); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
See relevant docs
@@ -151,7 +151,6 @@ describe("rollupResultLayer helper function", () => { | |||
// 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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I assume this was supposed to say 'cannot handle...' ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
@@ -3,9 +3,10 @@ import app from "../../../../server.js"; | |||
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
no vi
namespace so we have to explicitly import types like this one, as per docs
@@ -12,14 +12,16 @@ const invalidBody = { | |||
wrong: "message", | |||
}; | |||
|
|||
const mockSend = jest.fn(); | |||
jest.mock<typeof SlackNotify>("slack-notify", () => |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
tsc/vitest seem smart enough not to need the type declaration anymore 🤔
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Removed vultr server and associated DNS entries |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fantastic PR - thanks so much for the documentation links and explanations, super helpful.
"extends": [ | ||
"eslint:recommended", | ||
"plugin:@typescript-eslint/recommended", | ||
"prettier", | ||
"plugin:jest/recommended" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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?
@@ -151,7 +151,6 @@ describe("rollupResultLayer helper function", () => { | |||
// 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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.json
scripts etc)api.planx.uk
to usevitest
instead 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-summary
coverage 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
autoUpdate
flag, as per my comment invitest.config.ts
. But for now I've stuck with the singular minimum coverage threshold of 55% of functions.