Skip to content

Commit

Permalink
chore(api): Switch to use vitest over jest (#9853)
Browse files Browse the repository at this point in the history
We know that jest will likely become troublesome for us internally and
in individual user projects as we move forward with our epoch agenda.

This PR switches out just one package `api` to use vitest over jest for
the unit tests. The majority of the changes here are simply explicitly
importing vitest testing utilities.

Some notable points:
1. Style of using explicit imports over globals. I personally prefer
being explicit and have done that here. There is a note from vitest that
when not using globals other third party testing libraries might not
work (see:
https://vitest.dev/guide/migration.html#globals-as-a-default). We don't
use them here so we do not need to worry about that.
2. Hook execution is by default slightly different in vitest. They run
hooks like `beforeEach` in parallel rather than in series like jest
would (see: https://vitest.dev/guide/migration.html#hooks). I have
configured vitest to behave like jest and run them in series. We are
free to revisit this decision in the future - we would have to go
through the usage of each hook and confirm there are no cases where
running them in parallel would cause undesirable side effects.
3. In this package there were some `__mocks__` present. Vitest does not
load these by default and they must be explicitly loaded via
`vi.mock(...)`. See:
https://vitest.dev/guide/migration.html#auto-mocking-behaviour. It
appears that for this package these mocks actually served no purpose so
I have removed them here anyway.
4. CLI options are slightly different between jest and vitest. Vitest
has no `--colors` like jest (see: https://jestjs.io/docs/cli#--colors).
I have removed our usage of this option as I don't think we will
consider losing colors in non-tty environments a blocker. `maxWorkers`
is a CLI option in both vitest and jest however it appears that if you
wish to use it in vitest you must also specify a `minWorkers`. I have
updated appropriately with a `minWorkers=1`.

Specific test change:
1. I had to update the prisma client mock in
`packages/api/src/cache/__tests__/cacheFindMany.test.ts`. There already
exists a note within the appropriate source code that the
`PrismaClientValidationError` is not available until the prisma client
has been generated. I have added a mock `PrismaClientValidationError`
error and the tests pass as they used to.
  • Loading branch information
Josh-Walker-GM authored Jan 20, 2024
1 parent d78c1cd commit 4b541be
Show file tree
Hide file tree
Showing 28 changed files with 935 additions and 69 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ jobs:
uses: SimenB/github-actions-cpu-cores@v2

- name: 🧪 Test
run: yarn test-ci ${{ steps.cpu-cores.outputs.count }}
run: yarn test-ci --minWorkers=1 --maxWorkers=${{ steps.cpu-cores.outputs.count }}

build-lint-test-skip:
needs: detect-changes
Expand Down
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,8 @@
"release:notes": "node ./tasks/release/generateReleaseNotes.mjs",
"release:triage": "node ./tasks/release/triage/triage.mjs",
"smoke-tests": "node ./tasks/smoke-tests/smoke-tests.mjs",
"test": "nx run-many -t test -- --colors --maxWorkers=4",
"test-ci": "nx run-many -t test -- --colors --maxWorkers",
"test": "nx run-many -t test -- --minWorkers=1 --maxWorkers=4",
"test-ci": "nx run-many -t test",
"test:k6": "tsx ./tasks/k6-test/run-k6-tests.mts",
"test:types": "tstyche"
},
Expand Down
1 change: 0 additions & 1 deletion packages/api/__mocks__/@prisma/client.js

This file was deleted.

12 changes: 0 additions & 12 deletions packages/api/__mocks__/@redwoodjs/path.js

This file was deleted.

8 changes: 4 additions & 4 deletions packages/api/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,8 @@
"build:types": "tsc --build --verbose",
"build:watch": "nodemon --watch src --ext \"js,jsx,ts,tsx\" --ignore dist --exec \"yarn build\"",
"prepublishOnly": "NODE_ENV=production yarn build",
"test": "jest src",
"test:watch": "yarn test --watch"
"test": "vitest run src",
"test:watch": "vitest watch src"
},
"dependencies": {
"@babel/runtime-corejs3": "7.23.6",
Expand All @@ -50,12 +50,12 @@
"@types/memjs": "1",
"@types/pascalcase": "1.0.3",
"@types/split2": "4.2.3",
"jest": "29.7.0",
"memjs": "1.3.1",
"redis": "4.6.7",
"split2": "4.2.0",
"ts-toolbelt": "9.6.0",
"typescript": "5.3.3"
"typescript": "5.3.3",
"vitest": "1.2.1"
},
"peerDependencies": {
"memjs": "1.3.1",
Expand Down
1 change: 1 addition & 0 deletions packages/api/src/__tests__/normalizeRequest.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { Headers } from '@whatwg-node/fetch'
import type { APIGatewayProxyEvent } from 'aws-lambda'
import { test, expect } from 'vitest'

import { normalizeRequest } from '../transforms'

Expand Down
2 changes: 2 additions & 0 deletions packages/api/src/__tests__/transforms.test.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { describe, it, expect } from 'vitest'

import { removeNulls } from '../transforms'

describe('removeNulls utility', () => {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type { APIGatewayProxyEvent, Context } from 'aws-lambda'
import { describe, it, expect } from 'vitest'

import { getAuthenticationContext } from '../index'

Expand Down
2 changes: 2 additions & 0 deletions packages/api/src/auth/__tests__/parseJWT.test.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { describe, test, expect } from 'vitest'

import { parseJWT } from '../parseJWT'

const JWT_CLAIMS: Record<string, unknown> = {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { describe, test, expect } from 'vitest'

import { createVerifier, WebhookVerificationError } from '../index'

const stringPayload = 'No more secrets, Marty.'
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { describe, test, expect } from 'vitest'

import { createVerifier, WebhookVerificationError } from '../index'

const stringPayload = 'No more secrets, Marty.'
Expand Down
2 changes: 2 additions & 0 deletions packages/api/src/auth/verifiers/__tests__/jwtVerifier.test.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { describe, test, expect } from 'vitest'

import {
createVerifier,
WebhookSignError,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { beforeEach, afterEach, describe, test, vi, expect } from 'vitest'

import { createVerifier, WebhookVerificationError } from '../index'

const payload = 'No more secrets, Marty.'
Expand All @@ -6,11 +8,11 @@ const secret = 'MY_VOICE_IS_MY_PASSPORT_VERIFY_ME'
const { sign, verify } = createVerifier('secretKeyVerifier')

beforeEach(() => {
jest.spyOn(console, 'warn').mockImplementation(jest.fn())
vi.spyOn(console, 'warn').mockImplementation(vi.fn())
})

afterEach(() => {
jest.spyOn(console, 'warn').mockRestore()
vi.spyOn(console, 'warn').mockRestore()
})

describe('secretKey verifier', () => {
Expand All @@ -21,10 +23,10 @@ describe('secretKey verifier', () => {
})

test('it verifies that the secret and signature are identical', () => {
jest.spyOn(console, 'warn').mockImplementation(jest.fn())
vi.spyOn(console, 'warn').mockImplementation(vi.fn())
const signature = sign({ payload, secret })
expect(verify({ payload, secret, signature })).toBeTruthy()
jest.spyOn(console, 'warn').mockRestore()
vi.spyOn(console, 'warn').mockRestore()
})

test('it denies verification if the secret and signature are not the same', () => {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { describe, test, expect } from 'vitest'

import { createVerifier, WebhookVerificationError } from '../index'

const stringPayload = 'No more secrets, Marty.'
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { describe, expect, test } from 'vitest'

import { createVerifier, WebhookVerificationError } from '../index'

const stringPayload = 'No more secrets, Marty.'
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { beforeEach, afterEach, describe, test, expect, vi } from 'vitest'

import { createVerifier } from '../index'

const payload = 'No more secrets, Marty.'
Expand All @@ -6,11 +8,11 @@ const secret = 'MY_VOICE_IS_MY_PASSPORT_VERIFY_ME'
const { sign, verify } = createVerifier('skipVerifier')

beforeEach(() => {
jest.spyOn(console, 'warn').mockImplementation(jest.fn())
vi.spyOn(console, 'warn').mockImplementation(vi.fn())
})

afterEach(() => {
jest.spyOn(console, 'warn').mockRestore()
vi.spyOn(console, 'warn').mockRestore()
})

describe('skips verification verifier', () => {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
// import type { APIGatewayProxyEvent } from 'aws-lambda'

import { describe, test, expect } from 'vitest'

import { createVerifier, WebhookVerificationError } from '../index'

const payload = 'No more secrets, Marty.'
Expand Down
2 changes: 2 additions & 0 deletions packages/api/src/cache/__tests__/cache.test.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { describe, it, expect } from 'vitest'

import InMemoryClient from '../clients/InMemoryClient'
import { createCache } from '../index'

Expand Down
21 changes: 12 additions & 9 deletions packages/api/src/cache/__tests__/cacheFindMany.test.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,26 @@
import { PrismaClient } from '@prisma/client'
import { describe, afterEach, it, vi, expect } from 'vitest'

import InMemoryClient from '../clients/InMemoryClient'
import { createCache } from '../index'

const mockFindFirst = jest.fn()
const mockFindMany = jest.fn()
const mockFindFirst = vi.fn()
const mockFindMany = vi.fn()

jest.mock('@prisma/client', () => ({
PrismaClient: jest.fn(() => ({
vi.mock('@prisma/client', () => ({
PrismaClient: vi.fn(() => ({
user: {
findFirst: mockFindFirst,
findMany: mockFindMany,
},
})),
// NOTE: This is only available after `prisma generate` has been run
PrismaClientValidationError: new Error('PrismaClientValidationError'),
}))

describe('cacheFindMany', () => {
afterEach(() => {
jest.clearAllMocks()
vi.clearAllMocks()
})

it('adds the collection to the cache based on latest updated user', async () => {
Expand All @@ -33,7 +36,7 @@ describe('cacheFindMany', () => {

const client = new InMemoryClient()
const { cacheFindMany } = createCache(client)
const spy = jest.spyOn(client, 'set')
const spy = vi.spyOn(client, 'set')

await cacheFindMany('test', PrismaClient().user)

Expand Down Expand Up @@ -66,7 +69,7 @@ describe('cacheFindMany', () => {
mockFindMany.mockImplementation(() => [user])

const { cacheFindMany } = createCache(client)
const spy = jest.spyOn(client, 'set')
const spy = vi.spyOn(client, 'set')

await cacheFindMany('test', PrismaClient().user)

Expand All @@ -86,8 +89,8 @@ describe('cacheFindMany', () => {
mockFindFirst.mockImplementation(() => null)
mockFindMany.mockImplementation(() => [])
const { cacheFindMany } = createCache(client)
const getSpy = jest.spyOn(client, 'get')
const setSpy = jest.spyOn(client, 'set')
const getSpy = vi.spyOn(client, 'get')
const setSpy = vi.spyOn(client, 'set')

const result = await cacheFindMany('test', PrismaClient().user)

Expand Down
2 changes: 2 additions & 0 deletions packages/api/src/cache/__tests__/deleteCacheKey.test.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { describe, it, expect } from 'vitest'

import InMemoryClient from '../clients/InMemoryClient'
import { createCache } from '../index'

Expand Down
8 changes: 5 additions & 3 deletions packages/api/src/cache/__tests__/disconnect.test.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,22 @@
import { describe, beforeEach, it, expect, vi } from 'vitest'

import InMemoryClient from '../clients/InMemoryClient'
import { CacheTimeoutError } from '../errors'
import { createCache } from '../index'

describe('client.disconnect', () => {
beforeEach(() => {
jest.clearAllMocks()
vi.clearAllMocks()
})

it('attempts to disconnect on timeout error', async () => {
const client = new InMemoryClient()
const { cache } = createCache(client)
const getSpy = jest.spyOn(client, 'get')
const getSpy = vi.spyOn(client, 'get')
getSpy.mockImplementation(() => {
throw new CacheTimeoutError()
})
const disconnectSpy = jest.spyOn(client, 'disconnect')
const disconnectSpy = vi.spyOn(client, 'disconnect')

await cache('test', () => {
return { bar: 'baz' }
Expand Down
2 changes: 2 additions & 0 deletions packages/api/src/cache/__tests__/shared.test.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { describe, it, expect } from 'vitest'

import { createCache, formatCacheKey, InMemoryClient } from '../index'

describe('exports', () => {
Expand Down
1 change: 1 addition & 0 deletions packages/api/src/logger/logger.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import os from 'os'
import { join } from 'path'

import split from 'split2'
import { describe, test, expect } from 'vitest'

const pid = process.pid
const hostname = os.hostname()
Expand Down
14 changes: 8 additions & 6 deletions packages/api/src/validations/__tests__/validations.test.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { describe, it, expect, vi, beforeEach } from 'vitest'

import * as ValidationErrors from '../errors'
import {
validate,
Expand Down Expand Up @@ -1153,7 +1155,7 @@ describe('validate', () => {

describe('validateWithSync', () => {
it('runs a custom function as a validation', () => {
const validateFunction = jest.fn()
const validateFunction = vi.fn()
validateWithSync(validateFunction)

expect(validateFunction).toBeCalledWith()
Expand Down Expand Up @@ -1186,7 +1188,7 @@ describe('validateWithSync', () => {

describe('validateWith', () => {
it('runs a custom function as a validation', () => {
const validateFunction = jest.fn()
const validateFunction = vi.fn()
validateWith(validateFunction)

expect(validateFunction).toBeCalledWith()
Expand Down Expand Up @@ -1220,9 +1222,9 @@ describe('validateWith', () => {
// the actual methods of an instance of the class
//
// mockFindFirst.mockImplementation() to change what `findFirst()` would return
const mockFindFirst = jest.fn()
jest.mock('@prisma/client', () => ({
PrismaClient: jest.fn(() => ({
const mockFindFirst = vi.fn()
vi.mock('@prisma/client', () => ({
PrismaClient: vi.fn(() => ({
$transaction: async (func) =>
func({
user: {
Expand Down Expand Up @@ -1309,7 +1311,7 @@ describe('validateUniqueness', () => {
})

it('uses the given prisma client', async () => {
const mockFindFirstOther = jest.fn()
const mockFindFirstOther = vi.fn()
mockFindFirstOther.mockImplementation(() => ({
id: 2,
email: '[email protected]',
Expand Down
5 changes: 3 additions & 2 deletions packages/api/src/webhooks/webhooks.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type { APIGatewayProxyEvent } from 'aws-lambda'
import { beforeEach, afterEach, describe, test, expect, vi } from 'vitest'

import {
signPayload,
Expand Down Expand Up @@ -44,11 +45,11 @@ const buildEvent = ({
}

beforeEach(() => {
jest.spyOn(console, 'warn').mockImplementation(jest.fn())
vi.spyOn(console, 'warn').mockImplementation(vi.fn())
})

afterEach(() => {
jest.spyOn(console, 'warn').mockRestore()
vi.spyOn(console, 'warn').mockRestore()
})

describe('webhooks', () => {
Expand Down
13 changes: 13 additions & 0 deletions packages/api/vitest.config.mts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { defineConfig } from 'vitest/config'

export default defineConfig({
test: {
sequence: {
hooks: 'list',
},
setupFiles: [
'./vitest.setup.mts'
],
logHeapUsage: true,
},
})
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
module.exports = {}

// Set the default webhook secret for all tests
process.env = Object.assign(process.env, {
WEBHOOK_SECRET: 'MY_VOICE_IS_MY_PASSPORT_VERIFY_ME',
})
Loading

0 comments on commit 4b541be

Please sign in to comment.