From 25f0a2e1589e589f9b3ee47b85329c984748cf1d Mon Sep 17 00:00:00 2001
From: Gao Sun <gao@silverhand.io>
Date: Mon, 12 Dec 2022 13:43:23 +0800
Subject: [PATCH] test: use native ESM (#2621)

---
 .github/CODEOWNERS                            |   1 +
 .github/workflows/main.yml                    |   8 +-
 packages/cli/jest.config.js                   |  10 +
 packages/cli/jest.config.ts                   |  17 -
 packages/cli/jest.setup.ts                    |  19 -
 packages/cli/package.json                     |  11 +-
 .../database/alteration/index.test.ts         |  62 ++-
 .../src/commands/database/alteration/index.ts |  55 +--
 .../src/commands/database/alteration/utils.ts |  55 +++
 .../commands/database/alteration/version.ts   |   1 +
 packages/cli/src/queries/logto-config.test.ts |   1 +
 packages/cli/src/utilities.ts                 |   3 +-
 packages/cli/tsconfig.test.json               |   3 +-
 packages/console/package.json                 |   2 +-
 packages/core/jest.config.js                  |  14 +
 packages/core/jest.config.ts                  |  18 -
 packages/core/jest.setup.js                   |  39 ++
 packages/core/jest.setup.ts                   |  34 --
 packages/core/package.json                    |  14 +-
 packages/core/src/__mocks__/connector.ts      |   2 +
 packages/core/src/app/init.test.ts            |  49 +-
 .../src/connectors/utilities/index.test.ts    |  12 +-
 .../core/src/database/insert-into.test.ts     |   1 +
 .../core/src/database/update-where.test.ts    |   1 +
 packages/core/src/database/update-where.ts    |   2 +-
 .../core/src/i18n/detect-language.test.ts     |   3 +-
 packages/core/src/lib/passcode.test.ts        |  81 ++--
 packages/core/src/lib/phrase.test.ts          |   9 +-
 .../src/lib/sign-in-experience/index.test.ts  |  59 ++-
 .../lib/sign-in-experience/sign-up.test.ts    |   9 +-
 packages/core/src/lib/user.test.ts            |  24 +-
 packages/core/src/middleware/koa-auth.test.ts |  26 +-
 .../koa-connector-error-handler.test.ts       |   2 +
 .../src/middleware/koa-error-handler.test.ts  |   4 +-
 .../core/src/middleware/koa-guard.test.ts     |   6 +-
 .../core/src/middleware/koa-i18next.test.ts   |  11 +-
 .../src/middleware/koa-log-session.test.ts    |  18 +-
 packages/core/src/middleware/koa-log.test.ts  |  24 +-
 .../middleware/koa-oidc-error-handler.test.ts |   2 +
 .../src/middleware/koa-pagination.test.ts     |   5 +-
 .../src/middleware/koa-root-proxy.test.ts     |   2 +
 .../koa-slonik-error-handler.test.ts          |   2 +
 .../core/src/middleware/koa-spa-proxy.test.ts |  13 +-
 .../middleware/koa-spa-session-guard.test.ts  |  34 +-
 .../src/middleware/koa-welcome-proxy.test.ts  |  13 +-
 packages/core/src/oidc/adapter.test.ts        |  32 +-
 packages/core/src/queries/application.test.ts |   1 +
 packages/core/src/queries/connector.test.ts   |   1 +
 .../src/queries/oidc-model-instance.test.ts   |  23 +-
 packages/core/src/queries/passcode.test.ts    |   1 +
 packages/core/src/queries/resource.test.ts    |   1 +
 packages/core/src/queries/roles.test.ts       |   1 +
 packages/core/src/queries/setting.test.ts     |   1 +
 .../src/queries/sign-in-experience.test.ts    |   1 +
 packages/core/src/queries/user.test.ts        |   1 +
 packages/core/src/routes/admin-user.test.ts   |  88 ++--
 packages/core/src/routes/application.test.ts  |  17 +-
 packages/core/src/routes/authn.test.ts        |  40 +-
 packages/core/src/routes/connector.test.ts    | 130 +++--
 .../core/src/routes/connector.update.test.ts  |  51 +-
 .../core/src/routes/custom-phrase.test.ts     |  85 ++--
 packages/core/src/routes/dashboard.test.ts    |  40 +-
 .../actions/submit-interaction.test.ts        |  30 +-
 .../core/src/routes/interaction/index.test.ts | 110 +++--
 .../koa-interaction-body-guard.test.ts        |  13 +-
 ...oa-session-sign-inexperience-guard.test.ts |  35 +-
 .../utils/find-user-by-identifier.test.ts     |  28 +-
 .../utils/passcode-validation.test.ts         |  19 +-
 .../utils/social-verification.test.ts         |  10 +-
 .../identifier-payload-verification.test.ts   |  82 ++--
 .../mandatory-user-profile-validation.test.ts |  26 +-
 ...ofile-verification-forgot-password.test.ts |  27 +-
 ...profile-verification-profile-exist.test.ts |  30 +-
 ...le-verification-profile-registered.test.ts |  48 +-
 ...-verification-protected-identifier.test.ts |  21 +-
 .../user-identity-verification.test.ts        |  26 +-
 packages/core/src/routes/log.test.ts          |  26 +-
 .../routes/phrase.content-language.test.ts    |  47 +-
 packages/core/src/routes/phrase.test.ts       |  89 ++--
 packages/core/src/routes/profile.test.ts      | 198 ++++----
 packages/core/src/routes/profile.ts           |   3 -
 packages/core/src/routes/resource.test.ts     |  37 +-
 packages/core/src/routes/role.test.ts         |   6 +-
 .../core/src/routes/session/utils.test.ts     |   2 +-
 packages/core/src/routes/setting.test.ts      |  19 +-
 .../sign-in-experience.branding.guard.test.ts |  22 +-
 .../sign-in-experience.color.guard.test.ts    |  22 +-
 .../routes/sign-in-experience.guard.test.ts   |  32 +-
 .../src/routes/sign-in-experience.test.ts     |  62 +--
 packages/core/src/routes/swagger.test.ts      |  16 +-
 packages/core/src/routes/well-known.test.ts   |  65 ++-
 .../src/test-utils/jest-koa-mocks/LICENSE     |   9 +
 .../src/test-utils/jest-koa-mocks/README.md   |   3 +
 .../jest-koa-mocks/create-mock-context.ts     | 119 +++++
 .../jest-koa-mocks/create-mock-cookies.ts     |  37 ++
 packages/core/src/test-utils/oidc-provider.ts |  24 +
 .../oidc-provider-event-listener.test.ts      |  20 +-
 packages/core/src/utils/test-utils.ts         |   4 +-
 packages/shared/package.json                  |   8 +
 packages/shared/src/esm/index.ts              |   2 +
 packages/shared/src/esm/mock-esm.ts           |  72 +++
 .../shared/src/{utils => esm}/module-proxy.ts |   1 +
 .../shared/src/include.d/import-meta.d.ts     |  10 +
 packages/shared/src/utils/index.ts            |   1 -
 pnpm-lock.yaml                                | 455 +++++++++---------
 105 files changed, 1691 insertions(+), 1520 deletions(-)
 create mode 100644 packages/cli/jest.config.js
 delete mode 100644 packages/cli/jest.config.ts
 delete mode 100644 packages/cli/jest.setup.ts
 create mode 100644 packages/cli/src/commands/database/alteration/utils.ts
 create mode 100644 packages/core/jest.config.js
 delete mode 100644 packages/core/jest.config.ts
 create mode 100644 packages/core/jest.setup.js
 delete mode 100644 packages/core/jest.setup.ts
 create mode 100644 packages/core/src/test-utils/jest-koa-mocks/LICENSE
 create mode 100644 packages/core/src/test-utils/jest-koa-mocks/README.md
 create mode 100644 packages/core/src/test-utils/jest-koa-mocks/create-mock-context.ts
 create mode 100644 packages/core/src/test-utils/jest-koa-mocks/create-mock-cookies.ts
 create mode 100644 packages/core/src/test-utils/oidc-provider.ts
 create mode 100644 packages/shared/src/esm/index.ts
 create mode 100644 packages/shared/src/esm/mock-esm.ts
 rename packages/shared/src/{utils => esm}/module-proxy.ts (91%)
 create mode 100644 packages/shared/src/include.d/import-meta.d.ts

diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS
index 7fceb6eef..d9132021c 100644
--- a/.github/CODEOWNERS
+++ b/.github/CODEOWNERS
@@ -1,2 +1,3 @@
 /packages/schemas/tables @simeng-li @wangsijie
+/packages/core/src/routes/session @simeng-li @wangsijie
 /.changeset @gao-sun
diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml
index af5765f0d..ce7f5aeb4 100644
--- a/.github/workflows/main.yml
+++ b/.github/workflows/main.yml
@@ -20,20 +20,22 @@ jobs:
 
       - name: Setup Node and pnpm
         uses: silverhand-io/actions-node-pnpm-run-steps@v2
+        with:
+          node-version: 18
 
       - name: Build
         run: pnpm ci:build
 
   main-lint:
-    # avoid out of memory issue since macOS has bigger memory
-    # https://docs.github.com/en/actions/using-github-hosted-runners/about-github-hosted-runners#supported-runners-and-hardware-resources
-    runs-on: ubuntu-latest-4-cores
+    runs-on: ubuntu-latest
 
     steps:
       - uses: actions/checkout@v3
 
       - name: Setup Node and pnpm
         uses: silverhand-io/actions-node-pnpm-run-steps@v2
+        with:
+          node-version: 18
 
       - name: Prepack
         run: pnpm prepack
diff --git a/packages/cli/jest.config.js b/packages/cli/jest.config.js
new file mode 100644
index 000000000..652de5472
--- /dev/null
+++ b/packages/cli/jest.config.js
@@ -0,0 +1,10 @@
+const config = {
+  coveragePathIgnorePatterns: ['/node_modules/', '/src/__mocks__/'],
+  coverageReporters: ['text-summary', 'lcov'],
+  roots: ['./lib'],
+  moduleNameMapper: {
+    '^(chalk|inquirer)$': '<rootDir>/../shared/lib/esm/module-proxy.js',
+  },
+};
+
+export default config;
diff --git a/packages/cli/jest.config.ts b/packages/cli/jest.config.ts
deleted file mode 100644
index b6fcd166f..000000000
--- a/packages/cli/jest.config.ts
+++ /dev/null
@@ -1,17 +0,0 @@
-import type { Config } from '@silverhand/jest-config';
-import { merge } from '@silverhand/jest-config';
-
-const config: Config.InitialOptions = {
-  ...merge({
-    setupFilesAfterEnv: ['./jest.setup.ts'],
-    roots: ['./src'],
-    moduleNameMapper: {
-      '^(\\.{1,2}/.*)\\.js$': '$1',
-      '^(chalk|inquirer|ora)$': '<rootDir>/../shared/src/utils/module-proxy.ts',
-    },
-  }),
-  // Will update common config soon
-  transformIgnorePatterns: ['node_modules/(?!(.*(nanoid|jose|ky|@logto))/)'],
-};
-
-export default config;
diff --git a/packages/cli/jest.setup.ts b/packages/cli/jest.setup.ts
deleted file mode 100644
index 7ed7c3aaa..000000000
--- a/packages/cli/jest.setup.ts
+++ /dev/null
@@ -1,19 +0,0 @@
-/**
- * Mocking `import.meta.url` and `got` here since they inevitably needs native ESM, but jest is sticking with CJS.
- * Will figure out a way to run tests in native ESM mode.
- */
-
-jest.mock('./src/commands/database/alteration/meta-url.js', () => ({
-  metaUrl: 'file:///',
-}));
-
-jest.mock('./src/meta-url.js', () => ({
-  metaUrl: 'file:///',
-}));
-
-jest.mock('got', () => ({
-  got: {},
-}));
-
-// Make lint-staged happy
-export {};
diff --git a/packages/cli/package.json b/packages/cli/package.json
index afb40c820..6ddca2849 100644
--- a/packages/cli/package.json
+++ b/packages/cli/package.json
@@ -25,13 +25,14 @@
     "precommit": "lint-staged",
     "prepare:package-json": "node -p \"'export const packageJson = ' + JSON.stringify(require('./package.json'), undefined, 2) + ';'\" > src/package-json.ts",
     "build": "rimraf lib && pnpm prepare:package-json && tsc -p tsconfig.build.json",
+    "build:test": "rm -rf build/ && tsc -p tsconfig.test.json --sourcemap",
     "dev": "tsc -p tsconfig.build.json --watch --preserveWatchOutput --incremental",
     "start": "node .",
     "start:dev": "ts-node --files src/index.ts",
     "lint": "eslint --ext .ts src",
     "lint:report": "pnpm lint --format json --output-file report.json",
-    "test": "jest",
-    "test:ci": "jest",
+    "test": "pnpm build:test && NODE_OPTIONS=--experimental-vm-modules jest",
+    "test:ci": "pnpm run test",
     "prepack": "pnpm build"
   },
   "engines": {
@@ -52,7 +53,7 @@
     "hpagent": "^1.2.0",
     "inquirer": "^8.2.2",
     "nanoid": "^3.3.4",
-    "ora": "^5.0.0",
+    "ora": "^6.1.2",
     "p-retry": "^4.6.1",
     "roarr": "^7.11.0",
     "semver": "^7.3.8",
@@ -65,13 +66,13 @@
   },
   "devDependencies": {
     "@silverhand/eslint-config": "1.3.0",
-    "@silverhand/jest-config": "1.2.2",
     "@silverhand/ts-config": "1.2.1",
     "@types/fs-extra": "^9.0.13",
     "@types/inquirer": "^8.2.1",
     "@types/jest": "^29.1.2",
     "@types/node": "^16.0.0",
     "@types/semver": "^7.3.12",
+    "@types/sinon": "^10.0.13",
     "@types/tar": "^6.1.2",
     "@types/yargs": "^17.0.13",
     "eslint": "^8.21.0",
@@ -79,7 +80,7 @@
     "lint-staged": "^13.0.0",
     "prettier": "^2.7.1",
     "rimraf": "^3.0.2",
-    "ts-node": "^10.9.1",
+    "sinon": "^15.0.0",
     "typescript": "^4.7.4"
   },
   "eslintConfig": {
diff --git a/packages/cli/src/commands/database/alteration/index.test.ts b/packages/cli/src/commands/database/alteration/index.test.ts
index 9d546a63a..6033c4c23 100644
--- a/packages/cli/src/commands/database/alteration/index.test.ts
+++ b/packages/cli/src/commands/database/alteration/index.test.ts
@@ -1,42 +1,45 @@
+import { mockEsmWithActual } from '@logto/shared/esm';
+import Sinon from 'sinon';
 import { createMockPool } from 'slonik';
 
-import * as queries from '../../../queries/logto-config.js';
-import type { QueryType } from '../../../test-utilities.js';
-import * as functions from './index.js';
 import { chooseAlterationsByVersion } from './version.js';
 
-const mockQuery: jest.MockedFunction<QueryType> = jest.fn();
+const { jest } = import.meta;
 
 const pool = createMockPool({
-  query: async (sql, values) => {
-    return mockQuery(sql, values);
-  },
+  query: jest.fn(),
 });
 
+const files = Object.freeze([
+  { filename: '1.0.0-1663923770-a.js', path: '/alterations-js/1.0.0-1663923770-a.js' },
+  { filename: '1.0.0-1663923771-b.js', path: '/alterations-js/1.0.0-1663923771-b.js' },
+  { filename: '1.0.0-1663923772-c.js', path: '/alterations-js/1.0.0-1663923772-c.js' },
+]);
+
+await mockEsmWithActual('./utils.js', () => ({
+  getAlterationFiles: async () => files,
+}));
+
+const { getCurrentDatabaseAlterationTimestamp } = await mockEsmWithActual(
+  '../../../queries/logto-config.js',
+  () => ({
+    getCurrentDatabaseAlterationTimestamp: jest.fn(),
+  })
+);
+
+const { getUndeployedAlterations } = await import('./index.js');
+
 describe('getUndeployedAlterations()', () => {
-  const files = Object.freeze([
-    { filename: '1.0.0-1663923770-a.js', path: '/alterations-js/1.0.0-1663923770-a.js' },
-    { filename: '1.0.0-1663923771-b.js', path: '/alterations-js/1.0.0-1663923771-b.js' },
-    { filename: '1.0.0-1663923772-c.js', path: '/alterations-js/1.0.0-1663923772-c.js' },
-  ]);
-
-  beforeEach(() => {
-    // `getAlterationFiles()` will ensure the order
-    jest.spyOn(functions, 'getAlterationFiles').mockResolvedValueOnce([...files]);
-  });
-
   it('returns all files if database timestamp is 0', async () => {
-    jest.spyOn(queries, 'getCurrentDatabaseAlterationTimestamp').mockResolvedValueOnce(0);
+    getCurrentDatabaseAlterationTimestamp.mockResolvedValue(0);
 
-    await expect(functions.getUndeployedAlterations(pool)).resolves.toEqual(files);
+    await expect(getUndeployedAlterations(pool)).resolves.toEqual(files);
   });
 
   it('returns files whose timestamp is greater then database timestamp', async () => {
-    jest
-      .spyOn(queries, 'getCurrentDatabaseAlterationTimestamp')
-      .mockResolvedValueOnce(1_663_923_770);
+    getCurrentDatabaseAlterationTimestamp.mockResolvedValue(1_663_923_770);
 
-    await expect(functions.getUndeployedAlterations(pool)).resolves.toEqual([files[1], files[2]]);
+    await expect(getUndeployedAlterations(pool)).resolves.toEqual([files[1], files[2]]);
   });
 });
 
@@ -58,12 +61,19 @@ describe('chooseAlterationsByVersion()', () => {
       'next1-1663923781-c.js',
     ].map((filename) => ({ filename, path: '/alterations/' + filename }))
   );
+  const stub = Sinon.stub(global, 'process').value({ stdin: { isTTY: false } });
+
+  afterAll(() => {
+    stub.restore();
+  });
 
   it('chooses nothing when input version is invalid', async () => {
     await expect(chooseAlterationsByVersion(files, 'next1')).rejects.toThrow(
-      'Invalid Version: next1'
+      new TypeError('Invalid Version: next1')
+    );
+    await expect(chooseAlterationsByVersion([], 'ok')).rejects.toThrow(
+      new TypeError('Invalid Version: ok')
     );
-    await expect(chooseAlterationsByVersion([], 'ok')).rejects.toThrow('Invalid Version: ok');
   });
 
   it('chooses correct alteration files', async () => {
diff --git a/packages/cli/src/commands/database/alteration/index.ts b/packages/cli/src/commands/database/alteration/index.ts
index 61dd31262..17a93a3b1 100644
--- a/packages/cli/src/commands/database/alteration/index.ts
+++ b/packages/cli/src/commands/database/alteration/index.ts
@@ -1,11 +1,6 @@
-import { fileURLToPath } from 'node:url';
-import path from 'path';
-
 import type { AlterationScript } from '@logto/schemas/lib/types/alteration.js';
-import { findPackage } from '@logto/shared';
 import { conditionalString } from '@silverhand/essentials';
 import chalk from 'chalk';
-import fsExtra from 'fs-extra';
 import type { DatabasePool } from 'slonik';
 import type { CommandModule } from 'yargs';
 
@@ -14,25 +9,11 @@ import {
   getCurrentDatabaseAlterationTimestamp,
   updateDatabaseTimestamp,
 } from '../../../queries/logto-config.js';
-import { getPathInModule, log } from '../../../utilities.js';
-import { metaUrl } from './meta-url.js';
+import { log } from '../../../utilities.js';
 import type { AlterationFile } from './type.js';
+import { getAlterationFiles, getTimestampFromFilename } from './utils.js';
 import { chooseAlterationsByVersion } from './version.js';
 
-const currentDirname = path.dirname(fileURLToPath(metaUrl));
-const { copy, existsSync, remove, readdir } = fsExtra;
-const alterationFilenameRegex = /-(\d+)-?.*\.js$/;
-
-const getTimestampFromFilename = (filename: string) => {
-  const match = alterationFilenameRegex.exec(filename);
-
-  if (!match?.[1]) {
-    throw new Error(`Can not get timestamp: ${filename}`);
-  }
-
-  return Number(match[1]);
-};
-
 const importAlterationScript = async (filePath: string): Promise<AlterationScript> => {
   // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
   const module = await import(filePath);
@@ -41,38 +22,6 @@ const importAlterationScript = async (filePath: string): Promise<AlterationScrip
   return module.default as AlterationScript;
 };
 
-export const getAlterationFiles = async (): Promise<AlterationFile[]> => {
-  const alterationDirectory = getPathInModule('@logto/schemas', 'alterations-js');
-
-  /**
-   * We copy all alteration scripts to the CLI package root directory,
-   * since they need a proper context that includes required dependencies (such as slonik) in `node_modules/`.
-   * While the original `@logto/schemas` may remove them in production.
-   */
-  const packageDirectory = await findPackage(currentDirname);
-
-  const localAlterationDirectory = path.resolve(
-    packageDirectory ?? currentDirname,
-    'alteration-scripts'
-  );
-
-  if (!existsSync(alterationDirectory)) {
-    return [];
-  }
-
-  // We need to copy alteration files to execute in the CLI context to make `slonik` available
-  await remove(localAlterationDirectory);
-  await copy(alterationDirectory, localAlterationDirectory);
-
-  const directory = await readdir(localAlterationDirectory);
-  const files = directory.filter((file) => alterationFilenameRegex.test(file));
-
-  return files
-    .slice()
-    .sort((file1, file2) => getTimestampFromFilename(file1) - getTimestampFromFilename(file2))
-    .map((filename) => ({ path: path.join(localAlterationDirectory, filename), filename }));
-};
-
 export const getLatestAlterationTimestamp = async () => {
   const files = await getAlterationFiles();
   const lastFile = files[files.length - 1];
diff --git a/packages/cli/src/commands/database/alteration/utils.ts b/packages/cli/src/commands/database/alteration/utils.ts
new file mode 100644
index 000000000..6037b4fc2
--- /dev/null
+++ b/packages/cli/src/commands/database/alteration/utils.ts
@@ -0,0 +1,55 @@
+import { fileURLToPath } from 'node:url';
+import path from 'path';
+
+import { findPackage } from '@logto/shared';
+import fsExtra from 'fs-extra';
+
+import { getPathInModule } from '../../../utilities.js';
+import { metaUrl } from './meta-url.js';
+import type { AlterationFile } from './type.js';
+
+const currentDirname = path.dirname(fileURLToPath(metaUrl));
+const { copy, existsSync, remove, readdir } = fsExtra;
+const alterationFilenameRegex = /-(\d+)-?.*\.js$/;
+
+export const getTimestampFromFilename = (filename: string) => {
+  const match = alterationFilenameRegex.exec(filename);
+
+  if (!match?.[1]) {
+    throw new Error(`Can not get timestamp: ${filename}`);
+  }
+
+  return Number(match[1]);
+};
+
+export const getAlterationFiles = async (): Promise<AlterationFile[]> => {
+  const alterationDirectory = getPathInModule('@logto/schemas', 'alterations-js');
+
+  /**
+   * We copy all alteration scripts to the CLI package root directory,
+   * since they need a proper context that includes required dependencies (such as slonik) in `node_modules/`.
+   * While the original `@logto/schemas` may remove them in production.
+   */
+  const packageDirectory = await findPackage(currentDirname);
+
+  const localAlterationDirectory = path.resolve(
+    packageDirectory ?? currentDirname,
+    'alteration-scripts'
+  );
+
+  if (!existsSync(alterationDirectory)) {
+    return [];
+  }
+
+  // We need to copy alteration files to execute in the CLI context to make `slonik` available
+  await remove(localAlterationDirectory);
+  await copy(alterationDirectory, localAlterationDirectory);
+
+  const directory = await readdir(localAlterationDirectory);
+  const files = directory.filter((file) => alterationFilenameRegex.test(file));
+
+  return files
+    .slice()
+    .sort((file1, file2) => getTimestampFromFilename(file1) - getTimestampFromFilename(file2))
+    .map((filename) => ({ path: path.join(localAlterationDirectory, filename), filename }));
+};
diff --git a/packages/cli/src/commands/database/alteration/version.ts b/packages/cli/src/commands/database/alteration/version.ts
index 2b7f30747..5e0c01fa9 100644
--- a/packages/cli/src/commands/database/alteration/version.ts
+++ b/packages/cli/src/commands/database/alteration/version.ts
@@ -42,6 +42,7 @@ export const chooseAlterationsByVersion = async (
     .filter((version, index, self) => index === self.findIndex((another) => eq(version, another)))
     .slice()
     .sort((i, j) => compare(j, i));
+
   const initialSemVersion = conditional(
     initialVersion && initialVersion !== latestTag && new SemVer(initialVersion)
   );
diff --git a/packages/cli/src/queries/logto-config.test.ts b/packages/cli/src/queries/logto-config.test.ts
index 2762a56d5..c19c8d283 100644
--- a/packages/cli/src/queries/logto-config.test.ts
+++ b/packages/cli/src/queries/logto-config.test.ts
@@ -6,6 +6,7 @@ import type { QueryType } from '../test-utilities.js';
 import { expectSqlAssert } from '../test-utilities.js';
 import { updateDatabaseTimestamp, getCurrentDatabaseAlterationTimestamp } from './logto-config.js';
 
+const { jest } = import.meta;
 const mockQuery: jest.MockedFunction<QueryType> = jest.fn();
 
 const pool = createMockPool({
diff --git a/packages/cli/src/utilities.ts b/packages/cli/src/utilities.ts
index 96483a899..50d35f400 100644
--- a/packages/cli/src/utilities.ts
+++ b/packages/cli/src/utilities.ts
@@ -11,6 +11,7 @@ import type { Progress } from 'got';
 import { got } from 'got';
 import { HttpsProxyAgent } from 'hpagent';
 import inquirer from 'inquirer';
+import type { Options } from 'ora';
 import ora from 'ora';
 import { z } from 'zod';
 
@@ -92,7 +93,7 @@ export const getPathInModule = (moduleName: string, relativePath = '/') =>
 
 export const oraPromise = async <T>(
   promise: PromiseLike<T>,
-  options?: ora.Options,
+  options?: Options,
   exitOnError = false
 ) => {
   const spinner = ora(options).start();
diff --git a/packages/cli/tsconfig.test.json b/packages/cli/tsconfig.test.json
index aa0bf1ab7..55de18c33 100644
--- a/packages/cli/tsconfig.test.json
+++ b/packages/cli/tsconfig.test.json
@@ -3,5 +3,6 @@
   "compilerOptions": {
     "isolatedModules": false,
     "allowJs": true
-  }
+  },
+  "include": ["src"]
 }
diff --git a/packages/console/package.json b/packages/console/package.json
index ed5834780..636d76828 100644
--- a/packages/console/package.json
+++ b/packages/console/package.json
@@ -62,7 +62,7 @@
     "lint-staged": "^13.0.0",
     "lodash.get": "^4.4.2",
     "lodash.kebabcase": "^4.1.1",
-    "nanoid": "^3.1.23",
+    "nanoid": "^3.3.4",
     "parcel": "2.8.0",
     "postcss": "^8.4.6",
     "postcss-modules": "^4.3.0",
diff --git a/packages/core/jest.config.js b/packages/core/jest.config.js
new file mode 100644
index 000000000..fff6b08fe
--- /dev/null
+++ b/packages/core/jest.config.js
@@ -0,0 +1,14 @@
+/** @type {import('jest').Config} */
+const config = {
+  coveragePathIgnorePatterns: ['/node_modules/', '/src/__mocks__/'],
+  coverageReporters: ['text-summary', 'lcov'],
+  testPathIgnorePatterns: ['/node_modules/', '/build/routes/session/'], // `routes/session` is freezed
+  setupFilesAfterEnv: ['jest-matcher-specific-error', './jest.setup.js'],
+  roots: ['./build'],
+  moduleNameMapper: {
+    '^#src/(.*)\\.js(x)?$': '<rootDir>/build/$1',
+    '^(chalk|inquirer)$': '<rootDir>/../shared/lib/esm/module-proxy.js',
+  },
+};
+
+export default config;
diff --git a/packages/core/jest.config.ts b/packages/core/jest.config.ts
deleted file mode 100644
index 4888767bc..000000000
--- a/packages/core/jest.config.ts
+++ /dev/null
@@ -1,18 +0,0 @@
-import type { Config } from '@silverhand/jest-config';
-import { merge } from '@silverhand/jest-config';
-
-const config: Config.InitialOptions = {
-  ...merge({
-    testPathIgnorePatterns: ['/core/connectors/'],
-    setupFilesAfterEnv: ['jest-matcher-specific-error', './jest.setup.ts'],
-    moduleNameMapper: {
-      '^#src/(.*)\\.js(x)?$': '<rootDir>/src/$1',
-      '^(\\.{1,2}/.*)\\.js$': '$1',
-      '^(chalk|inquirer|ora)$': '<rootDir>/../shared/src/utils/module-proxy.ts',
-    },
-  }),
-  // Will update common config soon
-  transformIgnorePatterns: ['node_modules/(?!(.*(nanoid|jose|ky|@logto|got))/)'],
-};
-
-export default config;
diff --git a/packages/core/jest.setup.js b/packages/core/jest.setup.js
new file mode 100644
index 000000000..deb0c995e
--- /dev/null
+++ b/packages/core/jest.setup.js
@@ -0,0 +1,39 @@
+/**
+ * Setup environment variables for unit test
+ */
+
+import { mockEsm } from '@logto/shared/esm';
+import { createMockQueryResult, createMockPool } from 'slonik';
+
+const { jest } = import.meta;
+
+mockEsm('#src/env-set/index.js', () => ({
+  MountedApps: {
+    Api: 'api',
+    Oidc: 'oidc',
+    Console: 'console',
+    DemoApp: 'demo-app',
+    Welcome: 'welcome',
+  },
+  default: {
+    get values() {
+      return {
+        endpoint: 'https://logto.test',
+        adminConsoleUrl: 'https://logto.test/console',
+      };
+    },
+    get oidc() {
+      return {
+        issuer: 'https://logto.test/oidc',
+      };
+    },
+    get pool() {
+      return createMockPool({ query: async () => createMockQueryResult([]) });
+    },
+    load: jest.fn(),
+  },
+}));
+
+// Logger is not considered in all test cases
+// eslint-disable-next-line unicorn/consistent-function-scoping
+mockEsm('koa-logger', () => ({ default: () => (_, next) => next() }));
diff --git a/packages/core/jest.setup.ts b/packages/core/jest.setup.ts
deleted file mode 100644
index 2e91af71d..000000000
--- a/packages/core/jest.setup.ts
+++ /dev/null
@@ -1,34 +0,0 @@
-/**
- * Setup environment variables for unit test
- */
-
-import envSet from '#src/env-set/index.js';
-
-jest.mock('#src/lib/logto-config.js');
-jest.mock('#src/env-set/check-alteration-state.js');
-
-// eslint-disable-next-line unicorn/prefer-top-level-await
-(async () => {
-  await envSet.load();
-})();
-
-/**
- * Mocking `import.meta.url` and `got` here since they inevitably needs native ESM, but jest is sticking with CJS.
- * Will figure out a way to run tests in native ESM mode.
- */
-
-jest.mock('./src/connectors/meta-url.js', () => ({
-  metaUrl: 'file:///',
-}));
-
-jest.mock('../cli/lib/meta-url.js', () => ({
-  metaUrl: 'file:///',
-}));
-
-jest.mock('../cli/lib/commands/database/alteration/meta-url.js', () => ({
-  metaUrl: 'file:///',
-}));
-
-jest.mock('got', () => ({
-  got: {},
-}));
diff --git a/packages/core/package.json b/packages/core/package.json
index 6628584f9..23ab9455a 100644
--- a/packages/core/package.json
+++ b/packages/core/package.json
@@ -14,12 +14,14 @@
     "precommit": "lint-staged",
     "copyfiles": "copyfiles -u 1 src/**/*.md build",
     "build": "rm -rf build/ && tsc -p tsconfig.build.json && pnpm run copyfiles",
+    "build:test": "rm -rf build/ && tsc -p tsconfig.test.json --sourcemap",
     "lint": "eslint --ext .ts src",
     "lint:report": "pnpm lint --format json --output-file report.json",
     "dev": "rm -rf build/ && pnpm run copyfiles && nodemon",
     "start": "NODE_ENV=production node build/index.js",
-    "test": "jest",
-    "test:ci": "jest --coverage --silent",
+    "test:only": "NODE_OPTIONS=--experimental-vm-modules jest",
+    "test": "pnpm build:test && pnpm test:only build/",
+    "test:ci": "pnpm run test --coverage --silent",
     "test:report": "codecov -F core"
   },
   "dependencies": {
@@ -56,7 +58,7 @@
     "koa-router": "^12.0.0",
     "koa-send": "^5.0.1",
     "lodash.pick": "^4.4.0",
-    "nanoid": "^3.1.23",
+    "nanoid": "^3.3.4",
     "oidc-provider": "^7.13.0",
     "p-retry": "^4.6.1",
     "query-string": "^7.0.1",
@@ -69,9 +71,7 @@
     "zod": "^3.19.1"
   },
   "devDependencies": {
-    "@shopify/jest-koa-mocks": "^5.0.1",
     "@silverhand/eslint-config": "1.3.0",
-    "@silverhand/jest-config": "1.2.2",
     "@silverhand/ts-config": "1.2.1",
     "@types/debug": "^4.1.7",
     "@types/etag": "^1.8.1",
@@ -87,6 +87,7 @@
     "@types/lodash.pick": "^4.4.6",
     "@types/node": "^16.0.0",
     "@types/oidc-provider": "^7.12.0",
+    "@types/sinon": "^10.0.13",
     "@types/supertest": "^2.0.11",
     "copyfiles": "^2.4.1",
     "eslint": "^8.21.0",
@@ -94,10 +95,11 @@
     "jest": "^29.1.2",
     "jest-matcher-specific-error": "^1.0.0",
     "lint-staged": "^13.0.0",
-    "nock": "^13.2.2",
+    "node-mocks-http": "^1.12.1",
     "nodemon": "^2.0.19",
     "openapi-types": "^12.0.0",
     "prettier": "^2.7.1",
+    "sinon": "^15.0.0",
     "supertest": "^6.2.2",
     "typescript": "^4.9.3"
   },
diff --git a/packages/core/src/__mocks__/connector.ts b/packages/core/src/__mocks__/connector.ts
index a233f8eb3..db3db5267 100644
--- a/packages/core/src/__mocks__/connector.ts
+++ b/packages/core/src/__mocks__/connector.ts
@@ -33,6 +33,8 @@ export {
   mockMetadata3,
 } from './connector-base-data.js';
 
+const { jest } = import.meta;
+
 export const mockConnector: Connector = {
   id: 'id',
   config: {},
diff --git a/packages/core/src/app/init.test.ts b/packages/core/src/app/init.test.ts
index eea72195f..0ea5765f2 100644
--- a/packages/core/src/app/init.test.ts
+++ b/packages/core/src/app/init.test.ts
@@ -1,46 +1,39 @@
+import { mockEsmDefault, pickDefault } from '@logto/shared/esm';
 import Koa from 'koa';
 
-import * as koaErrorHandler from '#src/middleware/koa-error-handler.js';
-import * as koaI18next from '#src/middleware/koa-i18next.js';
-import * as koaLog from '#src/middleware/koa-log.js';
-import * as koaOIDCErrorHandler from '#src/middleware/koa-oidc-error-handler.js';
-import * as koaSlonikErrorHandler from '#src/middleware/koa-slonik-error-handler.js';
-import * as koaSpaProxy from '#src/middleware/koa-spa-proxy.js';
-import * as initOidc from '#src/oidc/init.js';
-import * as initRouter from '#src/routes/init.js';
+import { emptyMiddleware } from '#src/utils/test-utils.js';
 
-import initI18n from '../i18n/init.js';
-import initApp from './init.js';
+const { jest } = import.meta;
+
+const middlewareList = [
+  'error-handler',
+  'i18next',
+  'log',
+  'oidc-error-handler',
+  'slonik-error-handler',
+  'spa-proxy',
+].map((name) => {
+  const mock = jest.fn(() => emptyMiddleware);
+  mockEsmDefault(`#src/middleware/koa-${name}.js`, () => mock);
+
+  return mock;
+});
+
+const initI18n = await pickDefault(import('../i18n/init.js'));
+const initApp = await pickDefault(import('./init.js'));
 
 describe('App Init', () => {
   const listenMock = jest.spyOn(Koa.prototype, 'listen').mockImplementation(jest.fn());
 
-  const middlewareList = [
-    koaErrorHandler,
-    koaI18next,
-    koaLog,
-    koaOIDCErrorHandler,
-    koaSlonikErrorHandler,
-    koaSpaProxy,
-  ];
-  const initMethods = [initRouter, initOidc];
-
-  const middlewareSpys = middlewareList.map((module) => jest.spyOn(module, 'default'));
-  const initMethodSpys = initMethods.map((module) => jest.spyOn(module, 'default'));
-
   it('app init properly with 404 not found route', async () => {
     const app = new Koa();
     await initI18n();
     await initApp(app);
 
-    for (const middleware of middlewareSpys) {
+    for (const middleware of middlewareList) {
       expect(middleware).toBeCalled();
     }
 
-    for (const inits of initMethodSpys) {
-      expect(inits).toBeCalled();
-    }
-
     expect(listenMock).toBeCalled();
   });
 });
diff --git a/packages/core/src/connectors/utilities/index.test.ts b/packages/core/src/connectors/utilities/index.test.ts
index aec5cd4a7..25b409f23 100644
--- a/packages/core/src/connectors/utilities/index.test.ts
+++ b/packages/core/src/connectors/utilities/index.test.ts
@@ -1,8 +1,9 @@
 import type { Connector } from '@logto/schemas';
+import { mockEsmWithActual } from '@logto/shared/esm';
 
 import RequestError from '#src/errors/RequestError/index.js';
 
-import { getConnectorConfig } from './index.js';
+const { jest } = import.meta;
 
 const connectors: Connector[] = [
   {
@@ -15,13 +16,12 @@ const connectors: Connector[] = [
   },
 ];
 
-const findAllConnectors = jest.fn(async () => connectors);
-
-jest.mock('#src/queries/connector.js', () => ({
-  ...jest.requireActual('#src/queries/connector.js'),
-  findAllConnectors: async () => findAllConnectors(),
+await mockEsmWithActual('#src/queries/connector.js', () => ({
+  findAllConnectors: jest.fn(async () => connectors),
 }));
 
+const { getConnectorConfig } = await import('./index.js');
+
 it('getConnectorConfig() should return right config', async () => {
   const config = await getConnectorConfig('id');
   expect(config).toMatchObject({ foo: 'bar' });
diff --git a/packages/core/src/database/insert-into.test.ts b/packages/core/src/database/insert-into.test.ts
index 9d1b5c045..7585eb90b 100644
--- a/packages/core/src/database/insert-into.test.ts
+++ b/packages/core/src/database/insert-into.test.ts
@@ -9,6 +9,7 @@ import { createTestPool } from '#src/utils/test-utils.js';
 
 import { buildInsertInto } from './insert-into.js';
 
+const { jest } = import.meta;
 const poolSpy = jest.spyOn(envSet, 'pool', 'get');
 
 const buildExpectedInsertIntoSql = (keys: string[]) => [
diff --git a/packages/core/src/database/update-where.test.ts b/packages/core/src/database/update-where.test.ts
index 353f9f1bd..0595bf0c1 100644
--- a/packages/core/src/database/update-where.test.ts
+++ b/packages/core/src/database/update-where.test.ts
@@ -8,6 +8,7 @@ import { createTestPool } from '#src/utils/test-utils.js';
 
 import { buildUpdateWhere } from './update-where.js';
 
+const { jest } = import.meta;
 const poolSpy = jest.spyOn(envSet, 'pool', 'get');
 
 describe('buildUpdateWhere()', () => {
diff --git a/packages/core/src/database/update-where.ts b/packages/core/src/database/update-where.ts
index 23f7cd28b..99d3e74b4 100644
--- a/packages/core/src/database/update-where.ts
+++ b/packages/core/src/database/update-where.ts
@@ -1,6 +1,6 @@
 import type { SchemaLike, GeneratedSchema } from '@logto/schemas';
-import type { UpdateWhereData } from '@logto/shared';
 import { convertToIdentifiers, convertToPrimitiveOrSql, conditionalSql } from '@logto/shared';
+import type { UpdateWhereData } from '@logto/shared';
 import type { Truthy } from '@silverhand/essentials';
 import { notFalsy } from '@silverhand/essentials';
 import { sql } from 'slonik';
diff --git a/packages/core/src/i18n/detect-language.test.ts b/packages/core/src/i18n/detect-language.test.ts
index a0170e7ce..ab4e25814 100644
--- a/packages/core/src/i18n/detect-language.test.ts
+++ b/packages/core/src/i18n/detect-language.test.ts
@@ -1,6 +1,7 @@
-import { createMockContext } from '@shopify/jest-koa-mocks';
 import type { ParameterizedContext } from 'koa';
 
+import createMockContext from '#src/test-utils/jest-koa-mocks/create-mock-context.js';
+
 import detectLanguage from './detect-language.js';
 
 describe('detectLanguage', () => {
diff --git a/packages/core/src/lib/passcode.test.ts b/packages/core/src/lib/passcode.test.ts
index 0162c1aba..d4f531848 100644
--- a/packages/core/src/lib/passcode.test.ts
+++ b/packages/core/src/lib/passcode.test.ts
@@ -1,56 +1,47 @@
 import { ConnectorType } from '@logto/connector-kit';
 import type { Passcode } from '@logto/schemas';
 import { PasscodeType } from '@logto/schemas';
+import { mockEsm } from '@logto/shared/esm';
 import { any } from 'zod';
 
 import { mockConnector, mockMetadata } from '#src/__mocks__/index.js';
 import { defaultConnectorMethods } from '#src/connectors/consts.js';
-import { getLogtoConnectors } from '#src/connectors/index.js';
 import RequestError from '#src/errors/RequestError/index.js';
-import {
-  consumePasscode,
-  deletePasscodesByIds,
+
+const { jest } = import.meta;
+
+const {
   findUnconsumedPasscodeByJtiAndType,
   findUnconsumedPasscodesByJtiAndType,
+  deletePasscodesByIds,
   increasePasscodeTryCount,
   insertPasscode,
-} from '#src/queries/passcode.js';
+  consumePasscode,
+} = mockEsm('#src/queries/passcode.js', () => ({
+  findUnconsumedPasscodesByJtiAndType: jest.fn(),
+  findUnconsumedPasscodeByJtiAndType: jest.fn(),
+  deletePasscodesByIds: jest.fn(),
+  insertPasscode: jest.fn(),
+  consumePasscode: jest.fn(),
+  increasePasscodeTryCount: jest.fn(),
+}));
 
-import {
+const { getLogtoConnectors } = mockEsm('#src/connectors/index.js', () => ({
+  getLogtoConnectors: jest.fn(),
+}));
+
+const {
   createPasscode,
   passcodeExpiration,
   passcodeMaxTryCount,
   passcodeLength,
   sendPasscode,
   verifyPasscode,
-} from './passcode.js';
-
-jest.mock('#src/queries/passcode.js');
-jest.mock('#src/connectors.js');
-
-const mockedFindUnconsumedPasscodesByJtiAndType =
-  findUnconsumedPasscodesByJtiAndType as jest.MockedFunction<
-    typeof findUnconsumedPasscodesByJtiAndType
-  >;
-const mockedFindUnconsumedPasscodeByJtiAndType =
-  findUnconsumedPasscodeByJtiAndType as jest.MockedFunction<
-    typeof findUnconsumedPasscodeByJtiAndType
-  >;
-const mockedDeletePasscodesByIds = deletePasscodesByIds as jest.MockedFunction<
-  typeof deletePasscodesByIds
->;
-const mockedInsertPasscode = insertPasscode as jest.MockedFunction<typeof insertPasscode>;
-const mockedGetLogtoConnectors = getLogtoConnectors as jest.MockedFunction<
-  typeof getLogtoConnectors
->;
-const mockedConsumePasscode = consumePasscode as jest.MockedFunction<typeof consumePasscode>;
-const mockedIncreasePasscodeTryCount = increasePasscodeTryCount as jest.MockedFunction<
-  typeof increasePasscodeTryCount
->;
+} = await import('./passcode.js');
 
 beforeAll(() => {
-  mockedFindUnconsumedPasscodesByJtiAndType.mockResolvedValue([]);
-  mockedInsertPasscode.mockImplementation(async (data): Promise<Passcode> => {
+  findUnconsumedPasscodesByJtiAndType.mockResolvedValue([]);
+  insertPasscode.mockImplementation(async (data): Promise<Passcode> => {
     return {
       phone: null,
       email: null,
@@ -88,7 +79,7 @@ describe('createPasscode', () => {
   it('should disable existing passcode', async () => {
     const email = 'jony@example.com';
     const jti = 'jti';
-    mockedFindUnconsumedPasscodesByJtiAndType.mockResolvedValue([
+    findUnconsumedPasscodesByJtiAndType.mockResolvedValue([
       {
         id: 'id',
         interactionJti: jti,
@@ -104,7 +95,7 @@ describe('createPasscode', () => {
     await createPasscode(jti, PasscodeType.SignIn, {
       email,
     });
-    expect(mockedDeletePasscodesByIds).toHaveBeenCalledWith(['id']);
+    expect(deletePasscodesByIds).toHaveBeenCalledWith(['id']);
   });
 });
 
@@ -127,7 +118,7 @@ describe('sendPasscode', () => {
   });
 
   it('should throw error when email or sms connector can not be found', async () => {
-    mockedGetLogtoConnectors.mockResolvedValueOnce([
+    getLogtoConnectors.mockResolvedValueOnce([
       {
         ...defaultConnectorMethods,
         dbEntry: {
@@ -164,7 +155,7 @@ describe('sendPasscode', () => {
 
   it('should call sendPasscode with params matching', async () => {
     const sendMessage = jest.fn();
-    mockedGetLogtoConnectors.mockResolvedValueOnce([
+    getLogtoConnectors.mockResolvedValueOnce([
       {
         ...defaultConnectorMethods,
         configGuard: any(),
@@ -230,20 +221,20 @@ describe('verifyPasscode', () => {
   };
 
   it('should mark as consumed on successful verification', async () => {
-    mockedFindUnconsumedPasscodeByJtiAndType.mockResolvedValue(passcode);
+    findUnconsumedPasscodeByJtiAndType.mockResolvedValue(passcode);
     await verifyPasscode(passcode.interactionJti, passcode.type, passcode.code, { phone: 'phone' });
-    expect(mockedConsumePasscode).toHaveBeenCalledWith(passcode.id);
+    expect(consumePasscode).toHaveBeenCalledWith(passcode.id);
   });
 
   it('should fail when passcode not found', async () => {
-    mockedFindUnconsumedPasscodeByJtiAndType.mockResolvedValue(null);
+    findUnconsumedPasscodeByJtiAndType.mockResolvedValue(null);
     await expect(
       verifyPasscode(passcode.interactionJti, passcode.type, passcode.code, { phone: 'phone' })
     ).rejects.toThrow(new RequestError('passcode.not_found'));
   });
 
   it('should fail when phone mismatch', async () => {
-    mockedFindUnconsumedPasscodeByJtiAndType.mockResolvedValue(passcode);
+    findUnconsumedPasscodeByJtiAndType.mockResolvedValue(passcode);
     await expect(
       verifyPasscode(passcode.interactionJti, passcode.type, passcode.code, {
         phone: 'invalid_phone',
@@ -252,7 +243,7 @@ describe('verifyPasscode', () => {
   });
 
   it('should fail when email mismatch', async () => {
-    mockedFindUnconsumedPasscodeByJtiAndType.mockResolvedValue({
+    findUnconsumedPasscodeByJtiAndType.mockResolvedValue({
       ...passcode,
       phone: null,
       email: 'email',
@@ -265,7 +256,7 @@ describe('verifyPasscode', () => {
   });
 
   it('should fail when expired', async () => {
-    mockedFindUnconsumedPasscodeByJtiAndType.mockResolvedValue({
+    findUnconsumedPasscodeByJtiAndType.mockResolvedValue({
       ...passcode,
       createdAt: Date.now() - passcodeExpiration - 100,
     });
@@ -275,7 +266,7 @@ describe('verifyPasscode', () => {
   });
 
   it('should fail when exceed max count', async () => {
-    mockedFindUnconsumedPasscodeByJtiAndType.mockResolvedValue({
+    findUnconsumedPasscodeByJtiAndType.mockResolvedValue({
       ...passcode,
       tryCount: passcodeMaxTryCount,
     });
@@ -285,10 +276,10 @@ describe('verifyPasscode', () => {
   });
 
   it('should fail when invalid code, and should increase try_count', async () => {
-    mockedFindUnconsumedPasscodeByJtiAndType.mockResolvedValue(passcode);
+    findUnconsumedPasscodeByJtiAndType.mockResolvedValue(passcode);
     await expect(
       verifyPasscode(passcode.interactionJti, passcode.type, 'invalid', { phone: 'phone' })
     ).rejects.toThrow(new RequestError('passcode.code_mismatch'));
-    expect(mockedIncreasePasscodeTryCount).toHaveBeenCalledWith(passcode.id);
+    expect(increasePasscodeTryCount).toHaveBeenCalledWith(passcode.id);
   });
 });
diff --git a/packages/core/src/lib/phrase.test.ts b/packages/core/src/lib/phrase.test.ts
index 9d14d21bf..607bb7169 100644
--- a/packages/core/src/lib/phrase.test.ts
+++ b/packages/core/src/lib/phrase.test.ts
@@ -1,5 +1,6 @@
 import resource from '@logto/phrases-ui';
 import type { CustomPhrase } from '@logto/schemas';
+import { mockEsm } from '@logto/shared/esm';
 import deepmerge from 'deepmerge';
 
 import {
@@ -12,8 +13,8 @@ import {
   zhHkTag,
 } from '#src/__mocks__/custom-phrase.js';
 import RequestError from '#src/errors/RequestError/index.js';
-import { getPhrase } from '#src/lib/phrase.js';
 
+const { jest } = import.meta;
 const englishBuiltInPhrase = resource[enTag];
 
 const customOnlyLanguage = zhHkTag;
@@ -39,10 +40,12 @@ const findCustomPhraseByLanguageTag = jest.fn(async (languageTag: string) => {
   return mockCustomPhrase;
 });
 
-jest.mock('#src/queries/custom-phrase.js', () => ({
-  findCustomPhraseByLanguageTag: async (key: string) => findCustomPhraseByLanguageTag(key),
+mockEsm('#src/queries/custom-phrase.js', () => ({
+  findCustomPhraseByLanguageTag,
 }));
 
+const { getPhrase } = await import('#src/lib/phrase.js');
+
 afterEach(() => {
   jest.clearAllMocks();
 });
diff --git a/packages/core/src/lib/sign-in-experience/index.test.ts b/packages/core/src/lib/sign-in-experience/index.test.ts
index 710877d31..b41930a41 100644
--- a/packages/core/src/lib/sign-in-experience/index.test.ts
+++ b/packages/core/src/lib/sign-in-experience/index.test.ts
@@ -2,6 +2,7 @@ import type { LanguageTag } from '@logto/language-kit';
 import { builtInLanguages } from '@logto/phrases-ui';
 import type { CreateSignInExperience, SignInExperience } from '@logto/schemas';
 import { BrandingStyle } from '@logto/schemas';
+import { mockEsm } from '@logto/shared/esm';
 
 import {
   socialTarget01,
@@ -10,42 +11,36 @@ import {
   mockSignInExperience,
   mockSocialConnectors,
 } from '#src/__mocks__/index.js';
-import type { LogtoConnector } from '#src/connectors/types.js';
 import RequestError from '#src/errors/RequestError/index.js';
-import {
+
+const { jest } = import.meta;
+const allCustomLanguageTags: LanguageTag[] = [];
+
+const { findAllCustomLanguageTags } = mockEsm('#src/queries/custom-phrase.js', () => ({
+  findAllCustomLanguageTags: jest.fn(async () => allCustomLanguageTags),
+}));
+const { getLogtoConnectors } = mockEsm('#src/connectors.js', () => ({
+  getLogtoConnectors: jest.fn(),
+}));
+const { findDefaultSignInExperience, updateDefaultSignInExperience } = mockEsm(
+  '#src/queries/sign-in-experience.js',
+  () => ({
+    findDefaultSignInExperience: jest.fn(),
+    updateDefaultSignInExperience: jest.fn(
+      async (data: Partial<CreateSignInExperience>): Promise<SignInExperience> => ({
+        ...mockSignInExperience,
+        ...data,
+      })
+    ),
+  })
+);
+
+const {
   validateBranding,
   validateTermsOfUse,
   validateLanguageInfo,
   removeUnavailableSocialConnectorTargets,
-} from '#src/lib/sign-in-experience/index.js';
-import { updateDefaultSignInExperience } from '#src/queries/sign-in-experience.js';
-
-const allCustomLanguageTags: LanguageTag[] = [];
-const findAllCustomLanguageTags = jest.fn(async () => allCustomLanguageTags);
-const getLogtoConnectorsPlaceHolder = jest.fn() as jest.MockedFunction<
-  () => Promise<LogtoConnector[]>
->;
-const findDefaultSignInExperience = jest.fn() as jest.MockedFunction<
-  () => Promise<SignInExperience>
->;
-
-jest.mock('#src/queries/custom-phrase.js', () => ({
-  findAllCustomLanguageTags: async () => findAllCustomLanguageTags(),
-}));
-
-jest.mock('#src/connectors.js', () => ({
-  getLogtoConnectors: async () => getLogtoConnectorsPlaceHolder(),
-}));
-
-jest.mock('#src/queries/sign-in-experience.js', () => ({
-  findDefaultSignInExperience: async () => findDefaultSignInExperience(),
-  updateDefaultSignInExperience: jest.fn(
-    async (data: Partial<CreateSignInExperience>): Promise<SignInExperience> => ({
-      ...mockSignInExperience,
-      ...data,
-    })
-  ),
-}));
+} = await import('./index.js');
 
 beforeEach(() => {
   jest.clearAllMocks();
@@ -163,7 +158,7 @@ describe('remove unavailable social connector targets', () => {
       ...mockSignInExperience,
       socialSignInConnectorTargets: mockSocialConnectorTargets,
     });
-    getLogtoConnectorsPlaceHolder.mockResolvedValueOnce(mockSocialConnectors);
+    getLogtoConnectors.mockResolvedValueOnce(mockSocialConnectors);
     expect(mockSocialConnectorTargets).toEqual([socialTarget01, socialTarget02]);
     await removeUnavailableSocialConnectorTargets();
     expect(updateDefaultSignInExperience).toBeCalledWith({
diff --git a/packages/core/src/lib/sign-in-experience/sign-up.test.ts b/packages/core/src/lib/sign-in-experience/sign-up.test.ts
index 20b60caa1..8516237f1 100644
--- a/packages/core/src/lib/sign-in-experience/sign-up.test.ts
+++ b/packages/core/src/lib/sign-in-experience/sign-up.test.ts
@@ -1,17 +1,18 @@
 import { ConnectorType, SignInIdentifier } from '@logto/schemas';
+import { mockEsmWithActual } from '@logto/shared/esm';
 
 import { mockAliyunDmConnector, mockAliyunSmsConnector, mockSignUp } from '#src/__mocks__/index.js';
 import RequestError from '#src/errors/RequestError/index.js';
 
-import { validateSignUp } from './sign-up.js';
-
+const { jest } = import.meta;
 const enabledConnectors = [mockAliyunDmConnector, mockAliyunSmsConnector];
 
-jest.mock('#src/lib/session.js', () => ({
-  ...jest.requireActual('#src/lib/session.js'),
+await mockEsmWithActual('#src/lib/session.js', () => ({
   getApplicationIdFromInteraction: jest.fn(),
 }));
 
+const { validateSignUp } = await import('./sign-up.js');
+
 describe('validate sign-up', () => {
   describe('There must be at least one connector for the specific identifier.', () => {
     test('should throw when there is no email connector and identifier is email', async () => {
diff --git a/packages/core/src/lib/user.test.ts b/packages/core/src/lib/user.test.ts
index 91020dff6..54d75926b 100644
--- a/packages/core/src/lib/user.test.ts
+++ b/packages/core/src/lib/user.test.ts
@@ -1,20 +1,22 @@
 import { UsersPasswordEncryptionMethod } from '@logto/schemas';
+import { mockEsmWithActual } from '@logto/shared/esm';
 
-import { hasUserWithId, updateUserById } from '#src/queries/user.js';
+const { jest } = import.meta;
 
-import { encryptUserPassword, generateUserId } from './user.js';
+const { updateUserById, hasUserWithId } = await mockEsmWithActual('#src/queries/user.js', () => ({
+  updateUserById: jest.fn(),
+  hasUserWithId: jest.fn(),
+}));
 
-jest.mock('#src/queries/user.js');
+const { encryptUserPassword, generateUserId } = await import('./user.js');
 
 describe('generateUserId()', () => {
   afterEach(() => {
-    (hasUserWithId as jest.MockedFunction<typeof hasUserWithId>).mockClear();
+    hasUserWithId.mockClear();
   });
 
   it('generates user ID with correct length when no conflict found', async () => {
-    const mockedHasUserWithId = (
-      hasUserWithId as jest.MockedFunction<typeof hasUserWithId>
-    ).mockImplementationOnce(async () => false);
+    const mockedHasUserWithId = hasUserWithId.mockImplementationOnce(async () => false);
 
     await expect(generateUserId()).resolves.toHaveLength(12);
     expect(mockedHasUserWithId).toBeCalledTimes(1);
@@ -23,9 +25,7 @@ describe('generateUserId()', () => {
   it('generates user ID with correct length when retry limit is not reached', async () => {
     // eslint-disable-next-line @silverhand/fp/no-let
     let tried = 0;
-    const mockedHasUserWithId = (
-      hasUserWithId as jest.MockedFunction<typeof hasUserWithId>
-    ).mockImplementation(async () => {
+    const mockedHasUserWithId = hasUserWithId.mockImplementation(async () => {
       if (tried) {
         return false;
       }
@@ -41,9 +41,7 @@ describe('generateUserId()', () => {
   });
 
   it('rejects with correct error message when retry limit is reached', async () => {
-    const mockedHasUserWithId = (
-      hasUserWithId as jest.MockedFunction<typeof hasUserWithId>
-    ).mockImplementation(async () => true);
+    const mockedHasUserWithId = hasUserWithId.mockImplementation(async () => true);
 
     await expect(generateUserId(10)).rejects.toThrow(
       'Cannot generate user ID in reasonable retries'
diff --git a/packages/core/src/middleware/koa-auth.test.ts b/packages/core/src/middleware/koa-auth.test.ts
index 20510477c..a064bec7a 100644
--- a/packages/core/src/middleware/koa-auth.test.ts
+++ b/packages/core/src/middleware/koa-auth.test.ts
@@ -1,5 +1,5 @@
 import { UserRole } from '@logto/schemas';
-import { jwtVerify } from 'jose';
+import { mockEsm, pickDefault } from '@logto/shared/esm';
 import type { Context } from 'koa';
 import type { IRouterParamContext } from 'koa-router';
 
@@ -8,12 +8,15 @@ import RequestError from '#src/errors/RequestError/index.js';
 import { createContextWithRouteParameters } from '#src/utils/test-utils.js';
 
 import type { WithAuthContext } from './koa-auth.js';
-import koaAuth from './koa-auth.js';
 
-jest.mock('jose', () => ({
-  jwtVerify: jest.fn(() => ({ payload: { sub: 'fooUser', role_names: ['admin'] } })),
+const { jest } = import.meta;
+
+const { jwtVerify } = mockEsm('jose', () => ({
+  jwtVerify: jest.fn().mockReturnValue({ payload: { sub: 'fooUser', role_names: ['admin'] } }),
 }));
 
+const koaAuth = await pickDefault(import('./koa-auth.js'));
+
 describe('koaAuth middleware', () => {
   const baseCtx = createContextWithRouteParameters();
 
@@ -136,8 +139,7 @@ describe('koaAuth middleware', () => {
   });
 
   it('expect to throw if jwt sub is missing', async () => {
-    const mockJwtVerify = jwtVerify as jest.Mock;
-    mockJwtVerify.mockImplementationOnce(() => ({ payload: {} }));
+    jwtVerify.mockImplementationOnce(() => ({ payload: {} }));
 
     ctx.request = {
       ...ctx.request,
@@ -150,8 +152,7 @@ describe('koaAuth middleware', () => {
   });
 
   it('expect to have `client` type per jwt verify result', async () => {
-    const mockJwtVerify = jwtVerify as jest.Mock;
-    mockJwtVerify.mockImplementationOnce(() => ({ payload: { sub: 'bar', client_id: 'bar' } }));
+    jwtVerify.mockImplementationOnce(() => ({ payload: { sub: 'bar', client_id: 'bar' } }));
 
     ctx.request = {
       ...ctx.request,
@@ -165,8 +166,7 @@ describe('koaAuth middleware', () => {
   });
 
   it('expect to throw if jwt role_names is missing', async () => {
-    const mockJwtVerify = jwtVerify as jest.Mock;
-    mockJwtVerify.mockImplementationOnce(() => ({ payload: { sub: 'fooUser' } }));
+    jwtVerify.mockImplementationOnce(() => ({ payload: { sub: 'fooUser' } }));
 
     ctx.request = {
       ...ctx.request,
@@ -179,8 +179,7 @@ describe('koaAuth middleware', () => {
   });
 
   it('expect to throw if jwt role_names does not include admin', async () => {
-    const mockJwtVerify = jwtVerify as jest.Mock;
-    mockJwtVerify.mockImplementationOnce(() => ({
+    jwtVerify.mockImplementationOnce(() => ({
       payload: { sub: 'fooUser', role_names: ['foo'] },
     }));
 
@@ -195,8 +194,7 @@ describe('koaAuth middleware', () => {
   });
 
   it('expect to throw unauthorized error if unknown error occurs', async () => {
-    const mockJwtVerify = jwtVerify as jest.Mock;
-    mockJwtVerify.mockImplementationOnce(() => {
+    jwtVerify.mockImplementationOnce(() => {
       throw new Error('unknown error');
     });
     ctx.request = {
diff --git a/packages/core/src/middleware/koa-connector-error-handler.test.ts b/packages/core/src/middleware/koa-connector-error-handler.test.ts
index 7feae32a6..aa425465d 100644
--- a/packages/core/src/middleware/koa-connector-error-handler.test.ts
+++ b/packages/core/src/middleware/koa-connector-error-handler.test.ts
@@ -5,6 +5,8 @@ import { createContextWithRouteParameters } from '#src/utils/test-utils.js';
 
 import koaConnectorErrorHandler from './koa-connector-error-handler.js';
 
+const { jest } = import.meta;
+
 describe('koaConnectorErrorHandler middleware', () => {
   const next = jest.fn();
   const ctx = createContextWithRouteParameters();
diff --git a/packages/core/src/middleware/koa-error-handler.test.ts b/packages/core/src/middleware/koa-error-handler.test.ts
index d7646ab94..450ec4d26 100644
--- a/packages/core/src/middleware/koa-error-handler.test.ts
+++ b/packages/core/src/middleware/koa-error-handler.test.ts
@@ -1,10 +1,12 @@
-import { createMockContext } from '@shopify/jest-koa-mocks';
 import createHttpError from 'http-errors';
 
 import RequestError from '#src/errors/RequestError/index.js';
+import createMockContext from '#src/test-utils/jest-koa-mocks/create-mock-context.js';
 
 import koaErrorHandler from './koa-error-handler.js';
 
+const { jest } = import.meta;
+
 describe('koaErrorHandler middleware', () => {
   const mockBody = { data: 'foo' };
 
diff --git a/packages/core/src/middleware/koa-guard.test.ts b/packages/core/src/middleware/koa-guard.test.ts
index de0316f4d..8342be47c 100644
--- a/packages/core/src/middleware/koa-guard.test.ts
+++ b/packages/core/src/middleware/koa-guard.test.ts
@@ -1,10 +1,12 @@
+import { mockEsmDefault } from '@logto/shared/esm';
 import { z } from 'zod';
 
 import { emptyMiddleware, createContextWithRouteParameters } from '#src/utils/test-utils.js';
 
-import koaGuard, { isGuardMiddleware } from './koa-guard.js';
+const { jest } = import.meta;
 
-jest.mock('koa-body', () => emptyMiddleware);
+mockEsmDefault('koa-body', () => emptyMiddleware);
+const { default: koaGuard, isGuardMiddleware } = await import('./koa-guard.js');
 
 describe('koaGuardMiddleware', () => {
   describe('isGuardMiddleware', () => {
diff --git a/packages/core/src/middleware/koa-i18next.test.ts b/packages/core/src/middleware/koa-i18next.test.ts
index a7f79f5ef..1f6f4adf0 100644
--- a/packages/core/src/middleware/koa-i18next.test.ts
+++ b/packages/core/src/middleware/koa-i18next.test.ts
@@ -1,13 +1,14 @@
+import { mockEsmDefault, pickDefault } from '@logto/shared/esm';
 import i18next from 'i18next';
 
-import initI18n from '#src/i18n/init.js';
 import { createContextWithRouteParameters } from '#src/utils/test-utils.js';
 
-import koaI18next from './koa-i18next.js';
+const { jest } = import.meta;
+const mockLanguage = () => ['zh-cn'];
+mockEsmDefault('#src/i18n/detect-language.js', () => mockLanguage);
 
-// Can not access outter scope function in jest mock
-// eslint-disable-next-line unicorn/consistent-function-scoping
-jest.mock('#src/i18n/detect-language.js', () => () => ['zh-cn']);
+const initI18n = await pickDefault(import('#src/i18n/init.js'));
+const koaI18next = await pickDefault(import('./koa-i18next.js'));
 const changLanguageSpy = jest.spyOn(i18next, 'changeLanguage');
 
 describe('koaI18next', () => {
diff --git a/packages/core/src/middleware/koa-log-session.test.ts b/packages/core/src/middleware/koa-log-session.test.ts
index dea68604a..2cfb4467c 100644
--- a/packages/core/src/middleware/koa-log-session.test.ts
+++ b/packages/core/src/middleware/koa-log-session.test.ts
@@ -4,13 +4,10 @@ import koaLogSession from '#src/middleware/koa-log-session.js';
 import type { WithLogContext } from '#src/middleware/koa-log.js';
 import { createContextWithRouteParameters } from '#src/utils/test-utils.js';
 
-const interactionDetails: jest.MockedFunction<() => Promise<unknown>> = jest.fn(async () => ({}));
+const { jest } = import.meta;
 
-jest.mock('oidc-provider', () => ({
-  Provider: jest.fn(() => ({
-    interactionDetails,
-  })),
-}));
+const provider = new Provider('https://logto.test');
+const interactionDetails = jest.spyOn(provider, 'interactionDetails');
 
 describe('koaLogSession', () => {
   const sessionId = 'sessionId';
@@ -19,6 +16,7 @@ describe('koaLogSession', () => {
   const log = jest.fn();
   const next = jest.fn();
 
+  // @ts-expect-error for testing
   interactionDetails.mockResolvedValue({
     jti: sessionId,
     params: {
@@ -37,7 +35,7 @@ describe('koaLogSession', () => {
       log,
     };
 
-    await expect(koaLogSession(new Provider(''))(ctx, next)).resolves.not.toThrow();
+    await expect(koaLogSession(provider)(ctx, next)).resolves.not.toThrow();
     expect(interactionDetails).toHaveBeenCalled();
   });
 
@@ -48,7 +46,7 @@ describe('koaLogSession', () => {
       log,
     };
 
-    await expect(koaLogSession(new Provider(''))(ctx, next)).resolves.not.toThrow();
+    await expect(koaLogSession(provider)(ctx, next)).resolves.not.toThrow();
     expect(addLogContext).toHaveBeenCalledWith({ sessionId, applicationId });
   });
 
@@ -59,7 +57,7 @@ describe('koaLogSession', () => {
       log,
     };
 
-    await expect(koaLogSession(new Provider(''))(ctx, next)).resolves.not.toThrow();
+    await expect(koaLogSession(provider)(ctx, next)).resolves.not.toThrow();
     expect(next).toHaveBeenCalled();
   });
 
@@ -74,6 +72,6 @@ describe('koaLogSession', () => {
       throw new Error('message');
     });
 
-    await expect(koaLogSession(new Provider(''))(ctx, next)).resolves.not.toThrow();
+    await expect(koaLogSession(provider)(ctx, next)).resolves.not.toThrow();
   });
 });
diff --git a/packages/core/src/middleware/koa-log.test.ts b/packages/core/src/middleware/koa-log.test.ts
index af535c7cf..cedf4346f 100644
--- a/packages/core/src/middleware/koa-log.test.ts
+++ b/packages/core/src/middleware/koa-log.test.ts
@@ -1,29 +1,31 @@
 import type { LogPayload } from '@logto/schemas';
 import { LogResult } from '@logto/schemas';
+import { mockEsm, pickDefault } from '@logto/shared/esm';
 import i18next from 'i18next';
 
 import RequestError from '#src/errors/RequestError/index.js';
-import { insertLog } from '#src/queries/log.js';
 import { createContextWithRouteParameters } from '#src/utils/test-utils.js';
 
 import type { WithLogContext } from './koa-log.js';
-import koaLog from './koa-log.js';
+
+const { jest } = import.meta;
 
 const nanoIdMock = 'mockId';
 
 const addLogContext = jest.fn();
 const log = jest.fn();
 
-jest.mock('#src/queries/log.js', () => ({
-  insertLog: jest.fn(async () => 0),
+const { insertLog } = mockEsm('#src/queries/log.js', () => ({
+  insertLog: jest.fn(),
 }));
 
-jest.mock('nanoid', () => ({
-  nanoid: jest.fn(() => nanoIdMock),
+mockEsm('nanoid', () => ({
+  nanoid: () => nanoIdMock,
 }));
 
+const koaLog = await pickDefault(import('./koa-log.js'));
+
 describe('koaLog middleware', () => {
-  const insertLogMock = insertLog as jest.Mock;
   const type = 'SignInUsernamePassword';
   const mockPayload: LogPayload = {
     userId: 'foo',
@@ -54,7 +56,7 @@ describe('koaLog middleware', () => {
     };
     await koaLog()(ctx, next);
 
-    expect(insertLogMock).toBeCalledWith({
+    expect(insertLog).toBeCalledWith({
       id: nanoIdMock,
       type,
       payload: {
@@ -79,7 +81,7 @@ describe('koaLog middleware', () => {
     // eslint-disable-next-line unicorn/consistent-function-scoping, @typescript-eslint/no-empty-function
     const next = async () => {};
     await koaLog()(ctx, next);
-    expect(insertLogMock).not.toBeCalled();
+    expect(insertLog).not.toBeCalled();
   });
 
   describe('should insert an error log with the error message when next() throws an error', () => {
@@ -101,7 +103,7 @@ describe('koaLog middleware', () => {
       };
       await expect(koaLog()(ctx, next)).rejects.toMatchError(error);
 
-      expect(insertLogMock).toBeCalledWith({
+      expect(insertLog).toBeCalledWith({
         id: nanoIdMock,
         type,
         payload: {
@@ -135,7 +137,7 @@ describe('koaLog middleware', () => {
       };
       await expect(koaLog()(ctx, next)).rejects.toMatchError(error);
 
-      expect(insertLogMock).toBeCalledWith({
+      expect(insertLog).toBeCalledWith({
         id: nanoIdMock,
         type,
         payload: {
diff --git a/packages/core/src/middleware/koa-oidc-error-handler.test.ts b/packages/core/src/middleware/koa-oidc-error-handler.test.ts
index 36778fb4d..0dcd79460 100644
--- a/packages/core/src/middleware/koa-oidc-error-handler.test.ts
+++ b/packages/core/src/middleware/koa-oidc-error-handler.test.ts
@@ -5,6 +5,8 @@ import { createContextWithRouteParameters } from '#src/utils/test-utils.js';
 
 import koaOIDCErrorHandler from './koa-oidc-error-handler.js';
 
+const { jest } = import.meta;
+
 describe('koaOIDCErrorHandler middleware', () => {
   const next = jest.fn();
   const ctx = createContextWithRouteParameters();
diff --git a/packages/core/src/middleware/koa-pagination.test.ts b/packages/core/src/middleware/koa-pagination.test.ts
index 2f74fb0ad..3f7da06ad 100644
--- a/packages/core/src/middleware/koa-pagination.test.ts
+++ b/packages/core/src/middleware/koa-pagination.test.ts
@@ -1,9 +1,12 @@
-import { createMockContext } from '@shopify/jest-koa-mocks';
 import type { Context } from 'koa';
 
+import createMockContext from '#src/test-utils/jest-koa-mocks/create-mock-context.js';
+
 import type { WithPaginationContext } from './koa-pagination.js';
 import koaPagination from './koa-pagination.js';
 
+const { jest } = import.meta;
+
 const next = jest.fn();
 const setHeader = jest.fn();
 const links = new Set<string>();
diff --git a/packages/core/src/middleware/koa-root-proxy.test.ts b/packages/core/src/middleware/koa-root-proxy.test.ts
index 381f4bd0d..7105161e0 100644
--- a/packages/core/src/middleware/koa-root-proxy.test.ts
+++ b/packages/core/src/middleware/koa-root-proxy.test.ts
@@ -2,6 +2,8 @@ import { createContextWithRouteParameters } from '#src/utils/test-utils.js';
 
 import koaRootProxy from './koa-root-proxy.js';
 
+const { jest } = import.meta;
+
 describe('koaRootProxy', () => {
   const next = jest.fn();
 
diff --git a/packages/core/src/middleware/koa-slonik-error-handler.test.ts b/packages/core/src/middleware/koa-slonik-error-handler.test.ts
index 5e46ee1b6..6c189a4a2 100644
--- a/packages/core/src/middleware/koa-slonik-error-handler.test.ts
+++ b/packages/core/src/middleware/koa-slonik-error-handler.test.ts
@@ -7,6 +7,8 @@ import { createContextWithRouteParameters } from '#src/utils/test-utils.js';
 
 import koaSlonikErrorHandler from './koa-slonik-error-handler.js';
 
+const { jest } = import.meta;
+
 describe('koaSlonikErrorHandler middleware', () => {
   const next = jest.fn();
   const ctx = createContextWithRouteParameters();
diff --git a/packages/core/src/middleware/koa-spa-proxy.test.ts b/packages/core/src/middleware/koa-spa-proxy.test.ts
index b8b6a7ec4..e84cc7845 100644
--- a/packages/core/src/middleware/koa-spa-proxy.test.ts
+++ b/packages/core/src/middleware/koa-spa-proxy.test.ts
@@ -1,18 +1,21 @@
+import { mockEsmDefault, pickDefault } from '@logto/shared/esm';
+
 import envSet, { MountedApps } from '#src/env-set/index.js';
 import { createContextWithRouteParameters } from '#src/utils/test-utils.js';
 
-import koaSpaProxy from './koa-spa-proxy.js';
+const { jest } = import.meta;
 
 const mockProxyMiddleware = jest.fn();
 const mockStaticMiddleware = jest.fn();
 
-jest.mock('fs/promises', () => ({
-  ...jest.requireActual('fs/promises'),
+mockEsmDefault('fs/promises', () => ({
   readdir: jest.fn().mockResolvedValue(['sign-in']),
 }));
 
-jest.mock('koa-proxies', () => jest.fn(() => mockProxyMiddleware));
-jest.mock('#src/middleware/koa-serve-static.js', () => jest.fn(() => mockStaticMiddleware));
+mockEsmDefault('koa-proxies', () => jest.fn(() => mockProxyMiddleware));
+mockEsmDefault('#src/middleware/koa-serve-static.js', () => jest.fn(() => mockStaticMiddleware));
+
+const koaSpaProxy = await pickDefault(import('./koa-spa-proxy.js'));
 
 describe('koaSpaProxy middleware', () => {
   const envBackup = process.env;
diff --git a/packages/core/src/middleware/koa-spa-session-guard.test.ts b/packages/core/src/middleware/koa-spa-session-guard.test.ts
index 1a30e1496..f80c22475 100644
--- a/packages/core/src/middleware/koa-spa-session-guard.test.ts
+++ b/packages/core/src/middleware/koa-spa-session-guard.test.ts
@@ -1,23 +1,25 @@
+import { mockEsmWithActual } from '@logto/shared/esm';
 import { Provider } from 'oidc-provider';
 
 import { MountedApps } from '#src/env-set/index.js';
 import { createContextWithRouteParameters } from '#src/utils/test-utils.js';
 
-import koaSpaSessionGuard, { sessionNotFoundPath, guardedPath } from './koa-spa-session-guard.js';
+const { jest } = import.meta;
 
-jest.mock('fs/promises', () => ({
-  ...jest.requireActual('fs/promises'),
+await mockEsmWithActual('fs/promises', () => ({
   readdir: jest.fn().mockResolvedValue(['index.js']),
 }));
 
-jest.mock('oidc-provider', () => ({
-  Provider: jest.fn(() => ({
-    interactionDetails: jest.fn(),
-  })),
-}));
+const {
+  default: koaSpaSessionGuard,
+  sessionNotFoundPath,
+  guardedPath,
+} = await import('./koa-spa-session-guard.js');
 
 describe('koaSpaSessionGuard', () => {
   const envBackup = process.env;
+  const provider = new Provider('https://logto.test');
+  const interactionDetails = jest.spyOn(provider, 'interactionDetails');
 
   beforeEach(() => {
     process.env = { ...envBackup };
@@ -33,7 +35,6 @@ describe('koaSpaSessionGuard', () => {
   for (const app of Object.values(MountedApps)) {
     // eslint-disable-next-line @typescript-eslint/no-loop-func
     it(`${app} path should not redirect`, async () => {
-      const provider = new Provider('');
       const ctx = createContextWithRouteParameters({
         url: `/${app}/foo`,
       });
@@ -45,9 +46,7 @@ describe('koaSpaSessionGuard', () => {
   }
 
   it(`should not redirect for path ${sessionNotFoundPath}`, async () => {
-    const provider = new Provider('');
-
-    (provider.interactionDetails as jest.Mock).mockRejectedValue(new Error('session not found'));
+    interactionDetails.mockRejectedValue(new Error('session not found'));
     const ctx = createContextWithRouteParameters({
       url: `${sessionNotFoundPath}`,
     });
@@ -56,9 +55,7 @@ describe('koaSpaSessionGuard', () => {
   });
 
   it(`should not redirect for path /callback`, async () => {
-    const provider = new Provider('');
-
-    (provider.interactionDetails as jest.Mock).mockRejectedValue(new Error('session not found'));
+    interactionDetails.mockRejectedValue(new Error('session not found'));
     const ctx = createContextWithRouteParameters({
       url: '/callback/github',
     });
@@ -67,7 +64,8 @@ describe('koaSpaSessionGuard', () => {
   });
 
   it('should not redirect if session found', async () => {
-    const provider = new Provider('');
+    // @ts-expect-error for testing
+    interactionDetails.mockResolvedValue({});
     const ctx = createContextWithRouteParameters({
       url: `/sign-in`,
     });
@@ -78,9 +76,7 @@ describe('koaSpaSessionGuard', () => {
   for (const path of guardedPath) {
     // eslint-disable-next-line @typescript-eslint/no-loop-func
     it(`should redirect if session not found for ${path}`, async () => {
-      const provider = new Provider('');
-
-      (provider.interactionDetails as jest.Mock).mockRejectedValue(new Error('session not found'));
+      interactionDetails.mockRejectedValue(new Error('session not found'));
       const ctx = createContextWithRouteParameters({
         url: `${path}/foo`,
       });
diff --git a/packages/core/src/middleware/koa-welcome-proxy.test.ts b/packages/core/src/middleware/koa-welcome-proxy.test.ts
index ac79f9b2b..5750416aa 100644
--- a/packages/core/src/middleware/koa-welcome-proxy.test.ts
+++ b/packages/core/src/middleware/koa-welcome-proxy.test.ts
@@ -1,13 +1,16 @@
+import { mockEsm, pickDefault } from '@logto/shared/esm';
+
 import envSet, { MountedApps } from '#src/env-set/index.js';
-import { hasActiveUsers } from '#src/queries/user.js';
 import { createContextWithRouteParameters } from '#src/utils/test-utils.js';
 
-import koaWelcomeProxy from './koa-welcome-proxy.js';
+const { jest } = import.meta;
 
-jest.mock('#src/queries/user.js', () => ({
+const { hasActiveUsers } = mockEsm('#src/queries/user.js', () => ({
   hasActiveUsers: jest.fn(),
 }));
 
+const koaWelcomeProxy = await pickDefault(import('./koa-welcome-proxy.js'));
+
 describe('koaWelcomeProxy', () => {
   const next = jest.fn();
 
@@ -18,7 +21,7 @@ describe('koaWelcomeProxy', () => {
 
   it('should redirect to admin console if has AdminUsers', async () => {
     const { endpoint } = envSet.values;
-    (hasActiveUsers as jest.Mock).mockResolvedValue(true);
+    hasActiveUsers.mockResolvedValue(true);
     const ctx = createContextWithRouteParameters({
       url: `/${MountedApps.Welcome}`,
     });
@@ -31,7 +34,7 @@ describe('koaWelcomeProxy', () => {
 
   it('should redirect to welcome page if has no Users', async () => {
     const { endpoint } = envSet.values;
-    (hasActiveUsers as jest.Mock).mockResolvedValue(false);
+    hasActiveUsers.mockResolvedValue(false);
     const ctx = createContextWithRouteParameters({
       url: `/${MountedApps.Welcome}`,
     });
diff --git a/packages/core/src/oidc/adapter.test.ts b/packages/core/src/oidc/adapter.test.ts
index f2dc9526e..7d9f24c81 100644
--- a/packages/core/src/oidc/adapter.test.ts
+++ b/packages/core/src/oidc/adapter.test.ts
@@ -1,24 +1,18 @@
 import type { Application } from '@logto/schemas';
+import { mockEsm } from '@logto/shared/esm';
 import snakecaseKeys from 'snakecase-keys';
 
 import { mockApplication } from '#src/__mocks__/index.js';
-import {
-  consumeInstanceById,
-  destroyInstanceById,
-  findPayloadById,
-  findPayloadByPayloadField,
-  revokeInstanceByGrantId,
-  upsertInstance,
-} from '#src/queries/oidc-model-instance.js';
 
-import postgresAdapter from './adapter.js';
 import { getConstantClientMetadata } from './utils.js';
 
-jest.mock('#src/queries/application.js', () => ({
+const { jest } = import.meta;
+
+mockEsm('#src/queries/application.js', () => ({
   findApplicationById: jest.fn(async (): Promise<Application> => mockApplication),
 }));
 
-jest.mock('#src/queries/oidc-model-instance.js', () => ({
+mockEsm('#src/queries/oidc-model-instance.js', () => ({
   upsertInstance: jest.fn(),
   findPayloadById: jest.fn(),
   findPayloadByPayloadField: jest.fn(),
@@ -27,15 +21,25 @@ jest.mock('#src/queries/oidc-model-instance.js', () => ({
   revokeInstanceByGrantId: jest.fn(),
 }));
 
-const now = Date.now();
-
-jest.mock(
+mockEsm(
   'date-fns',
   jest.fn(() => ({
     addSeconds: jest.fn((_: Date, seconds: number) => new Date(now + seconds * 1000)),
   }))
 );
 
+const { default: postgresAdapter } = await import('./adapter.js');
+const {
+  consumeInstanceById,
+  destroyInstanceById,
+  findPayloadById,
+  findPayloadByPayloadField,
+  revokeInstanceByGrantId,
+  upsertInstance,
+} = await import('#src/queries/oidc-model-instance.js');
+
+const now = Date.now();
+
 describe('postgres Adapter', () => {
   it('Client Modal', async () => {
     const rejectError = new Error('Not implemented');
diff --git a/packages/core/src/queries/application.test.ts b/packages/core/src/queries/application.test.ts
index 794388d38..87fa00b5e 100644
--- a/packages/core/src/queries/application.test.ts
+++ b/packages/core/src/queries/application.test.ts
@@ -18,6 +18,7 @@ import {
   deleteApplicationById,
 } from './application.js';
 
+const { jest } = import.meta;
 const mockQuery: jest.MockedFunction<QueryType> = jest.fn();
 
 jest.spyOn(envSet, 'pool', 'get').mockReturnValue(
diff --git a/packages/core/src/queries/connector.test.ts b/packages/core/src/queries/connector.test.ts
index 3aa1ca6b6..05b3169b9 100644
--- a/packages/core/src/queries/connector.test.ts
+++ b/packages/core/src/queries/connector.test.ts
@@ -18,6 +18,7 @@ import {
   updateConnector,
 } from './connector.js';
 
+const { jest } = import.meta;
 const mockQuery: jest.MockedFunction<QueryType> = jest.fn();
 
 jest.spyOn(envSet, 'pool', 'get').mockReturnValue(
diff --git a/packages/core/src/queries/oidc-model-instance.test.ts b/packages/core/src/queries/oidc-model-instance.test.ts
index cbdc4e1ea..3d6366a45 100644
--- a/packages/core/src/queries/oidc-model-instance.test.ts
+++ b/packages/core/src/queries/oidc-model-instance.test.ts
@@ -1,21 +1,14 @@
 import type { CreateOidcModelInstance } from '@logto/schemas';
 import { OidcModelInstances } from '@logto/schemas';
 import { convertToIdentifiers } from '@logto/shared';
+import { mockEsmWithActual } from '@logto/shared/esm';
 import { createMockPool, createMockQueryResult, sql } from 'slonik';
 
 import envSet from '#src/env-set/index.js';
 import type { QueryType } from '#src/utils/test-utils.js';
 import { expectSqlAssert } from '#src/utils/test-utils.js';
 
-import {
-  upsertInstance,
-  findPayloadById,
-  findPayloadByPayloadField,
-  consumeInstanceById,
-  destroyInstanceById,
-  revokeInstanceByGrantId,
-} from './oidc-model-instance.js';
-
+const { jest } = import.meta;
 const mockQuery: jest.MockedFunction<QueryType> = jest.fn();
 
 jest.spyOn(envSet, 'pool', 'get').mockReturnValue(
@@ -26,11 +19,19 @@ jest.spyOn(envSet, 'pool', 'get').mockReturnValue(
   })
 );
 
-jest.mock('@logto/shared', () => ({
-  ...jest.requireActual('@logto/shared'),
+await mockEsmWithActual('@logto/shared', () => ({
   convertToTimestamp: () => 100,
 }));
 
+const {
+  upsertInstance,
+  findPayloadById,
+  findPayloadByPayloadField,
+  consumeInstanceById,
+  destroyInstanceById,
+  revokeInstanceByGrantId,
+} = await import('./oidc-model-instance.js');
+
 describe('oidc-model-instance query', () => {
   const { table, fields } = convertToIdentifiers(OidcModelInstances);
   const expiresAt = Date.now();
diff --git a/packages/core/src/queries/passcode.test.ts b/packages/core/src/queries/passcode.test.ts
index 1b26c5a7d..283e1aee1 100644
--- a/packages/core/src/queries/passcode.test.ts
+++ b/packages/core/src/queries/passcode.test.ts
@@ -17,6 +17,7 @@ import {
   deletePasscodesByIds,
 } from './passcode.js';
 
+const { jest } = import.meta;
 const mockQuery: jest.MockedFunction<QueryType> = jest.fn();
 
 jest.spyOn(envSet, 'pool', 'get').mockReturnValue(
diff --git a/packages/core/src/queries/resource.test.ts b/packages/core/src/queries/resource.test.ts
index 25a74e8b0..20d3cd0f8 100644
--- a/packages/core/src/queries/resource.test.ts
+++ b/packages/core/src/queries/resource.test.ts
@@ -18,6 +18,7 @@ import {
   deleteResourceById,
 } from './resource.js';
 
+const { jest } = import.meta;
 const mockQuery: jest.MockedFunction<QueryType> = jest.fn();
 
 jest.spyOn(envSet, 'pool', 'get').mockReturnValue(
diff --git a/packages/core/src/queries/roles.test.ts b/packages/core/src/queries/roles.test.ts
index e24e414b0..b024b3bb9 100644
--- a/packages/core/src/queries/roles.test.ts
+++ b/packages/core/src/queries/roles.test.ts
@@ -9,6 +9,7 @@ import { expectSqlAssert } from '#src/utils/test-utils.js';
 
 import { findAllRoles, findRolesByRoleNames } from './roles.js';
 
+const { jest } = import.meta;
 const mockQuery: jest.MockedFunction<QueryType> = jest.fn();
 
 jest.spyOn(envSet, 'pool', 'get').mockReturnValue(
diff --git a/packages/core/src/queries/setting.test.ts b/packages/core/src/queries/setting.test.ts
index 7eb89ff52..ef4a2e800 100644
--- a/packages/core/src/queries/setting.test.ts
+++ b/packages/core/src/queries/setting.test.ts
@@ -9,6 +9,7 @@ import { expectSqlAssert } from '#src/utils/test-utils.js';
 
 import { defaultSettingId, getSetting, updateSetting } from './setting.js';
 
+const { jest } = import.meta;
 const mockQuery: jest.MockedFunction<QueryType> = jest.fn();
 
 jest.spyOn(envSet, 'pool', 'get').mockReturnValue(
diff --git a/packages/core/src/queries/sign-in-experience.test.ts b/packages/core/src/queries/sign-in-experience.test.ts
index 8c4f81f8e..f2977c95a 100644
--- a/packages/core/src/queries/sign-in-experience.test.ts
+++ b/packages/core/src/queries/sign-in-experience.test.ts
@@ -10,6 +10,7 @@ import {
   updateDefaultSignInExperience,
 } from './sign-in-experience.js';
 
+const { jest } = import.meta;
 const mockQuery: jest.MockedFunction<QueryType> = jest.fn();
 
 jest.spyOn(envSet, 'pool', 'get').mockReturnValue(
diff --git a/packages/core/src/queries/user.test.ts b/packages/core/src/queries/user.test.ts
index b488619b9..523c514ca 100644
--- a/packages/core/src/queries/user.test.ts
+++ b/packages/core/src/queries/user.test.ts
@@ -26,6 +26,7 @@ import {
   deleteUserIdentity,
 } from './user.js';
 
+const { jest } = import.meta;
 const mockQuery: jest.MockedFunction<QueryType> = jest.fn();
 
 jest.spyOn(envSet, 'pool', 'get').mockReturnValue(
diff --git a/packages/core/src/routes/admin-user.test.ts b/packages/core/src/routes/admin-user.test.ts
index b0711b92e..050cc3ab8 100644
--- a/packages/core/src/routes/admin-user.test.ts
+++ b/packages/core/src/routes/admin-user.test.ts
@@ -1,5 +1,6 @@
 import type { CreateUser, Role, User } from '@logto/schemas';
 import { userInfoSelectFields } from '@logto/schemas';
+import { mockEsm, mockEsmWithActual, pickDefault } from '@logto/shared/esm';
 import pick from 'lodash.pick';
 
 import {
@@ -8,19 +9,9 @@ import {
   mockUserListResponse,
   mockUserResponse,
 } from '#src/__mocks__/index.js';
-import { encryptUserPassword } from '#src/lib/user.js';
-import { findRolesByRoleNames } from '#src/queries/roles.js';
-import {
-  hasUser,
-  findUserById,
-  updateUserById,
-  deleteUserIdentity,
-  deleteUserById,
-} from '#src/queries/user.js';
 import { createRequester } from '#src/utils/test-utils.js';
 
-import adminUserRoutes from './admin-user.js';
-
+const { jest } = import.meta;
 const filterUsersWithSearch = (users: User[], search: string) =>
   users.filter((user) =>
     [user.username, user.primaryEmail, user.primaryPhone, user.name].some((value) =>
@@ -28,45 +19,44 @@ const filterUsersWithSearch = (users: User[], search: string) =>
     )
   );
 
-const mockFindDefaultSignInExperience = jest.fn(async () => ({
-  signUp: {
-    identifiers: [],
-    password: false,
-    verify: false,
-  },
-}));
-
-jest.mock('#src/queries/sign-in-experience.js', () => ({
-  findDefaultSignInExperience: jest.fn(async () => mockFindDefaultSignInExperience()),
+mockEsm('#src/queries/sign-in-experience.js', () => ({
+  findDefaultSignInExperience: jest.fn(async () => ({
+    signUp: {
+      identifiers: [],
+      password: false,
+      verify: false,
+    },
+  })),
 }));
 
 const mockHasUser = jest.fn(async () => false);
 const mockHasUserWithEmail = jest.fn(async () => false);
 const mockHasUserWithPhone = jest.fn(async () => false);
-jest.mock('#src/queries/user.js', () => ({
-  countUsers: jest.fn(async (search) => ({
-    count: search ? filterUsersWithSearch(mockUserList, search).length : mockUserList.length,
-  })),
-  findUsers: jest.fn(
-    async (limit, offset, search): Promise<User[]> =>
-      search ? filterUsersWithSearch(mockUserList, search) : mockUserList
-  ),
-  findUserById: jest.fn(async (): Promise<User> => mockUser),
-  hasUser: jest.fn(async () => mockHasUser()),
-  hasUserWithEmail: jest.fn(async () => mockHasUserWithEmail()),
-  hasUserWithPhone: jest.fn(async () => mockHasUserWithPhone()),
-  updateUserById: jest.fn(
-    async (_, data: Partial<CreateUser>): Promise<User> => ({
-      ...mockUser,
-      ...data,
-    })
-  ),
-  deleteUserById: jest.fn(),
-  deleteUserIdentity: jest.fn(),
-}));
 
-jest.mock('#src/lib/user.js', () => ({
-  ...jest.requireActual('#src/lib/user.js'),
+const { hasUser, findUserById, updateUserById, deleteUserIdentity, deleteUserById } =
+  await mockEsmWithActual('#src/queries/user.js', () => ({
+    countUsers: jest.fn(async (search) => ({
+      count: search ? filterUsersWithSearch(mockUserList, search).length : mockUserList.length,
+    })),
+    findUsers: jest.fn(
+      async (limit, offset, search): Promise<User[]> =>
+        search ? filterUsersWithSearch(mockUserList, search) : mockUserList
+    ),
+    findUserById: jest.fn(async (): Promise<User> => mockUser),
+    hasUser: jest.fn(async () => mockHasUser()),
+    hasUserWithEmail: jest.fn(async () => mockHasUserWithEmail()),
+    hasUserWithPhone: jest.fn(async () => mockHasUserWithPhone()),
+    updateUserById: jest.fn(
+      async (_, data: Partial<CreateUser>): Promise<User> => ({
+        ...mockUser,
+        ...data,
+      })
+    ),
+    deleteUserById: jest.fn(),
+    deleteUserIdentity: jest.fn(),
+  }));
+
+const { encryptUserPassword } = await mockEsmWithActual('#src/lib/user.js', () => ({
   generateUserId: jest.fn(() => 'fooId'),
   encryptUserPassword: jest.fn(() => ({
     passwordEncrypted: 'password',
@@ -80,18 +70,18 @@ jest.mock('#src/lib/user.js', () => ({
   ),
 }));
 
-jest.mock('#src/queries/roles.js', () => ({
+const { findRolesByRoleNames } = mockEsm('#src/queries/roles.js', () => ({
   findRolesByRoleNames: jest.fn(
     async (): Promise<Role[]> => [{ id: 'role_id', name: 'admin', description: 'none' }]
   ),
 }));
 
-const revokeInstanceByUserId = jest.fn();
-jest.mock('#src/queries/oidc-model-instance.js', () => ({
-  revokeInstanceByUserId: async (modelName: string, userId: string) =>
-    revokeInstanceByUserId(modelName, userId),
+const { revokeInstanceByUserId } = mockEsm('#src/queries/oidc-model-instance.js', () => ({
+  revokeInstanceByUserId: jest.fn(),
 }));
 
+const adminUserRoutes = await pickDefault(import('./admin-user.js'));
+
 describe('adminUserRoutes', () => {
   const userRequest = createRequester({ authedRoutes: adminUserRoutes });
 
diff --git a/packages/core/src/routes/application.test.ts b/packages/core/src/routes/application.test.ts
index 863e4e25f..5106240df 100644
--- a/packages/core/src/routes/application.test.ts
+++ b/packages/core/src/routes/application.test.ts
@@ -1,13 +1,12 @@
 import type { Application, CreateApplication } from '@logto/schemas';
 import { ApplicationType } from '@logto/schemas';
+import { mockEsm, pickDefault } from '@logto/shared/esm';
 
 import { mockApplication } from '#src/__mocks__/index.js';
-import { findApplicationById } from '#src/queries/application.js';
-import { createRequester } from '#src/utils/test-utils.js';
 
-import applicationRoutes from './application.js';
+const { jest } = import.meta;
 
-jest.mock('#src/queries/application.js', () => ({
+const { findApplicationById } = mockEsm('#src/queries/application.js', () => ({
   findTotalNumberOfApplications: jest.fn(async () => ({ count: 10 })),
   findAllApplications: jest.fn(async () => [mockApplication]),
   findApplicationById: jest.fn(async () => mockApplication),
@@ -30,23 +29,21 @@ jest.mock('#src/queries/application.js', () => ({
   ),
 }));
 
-jest.mock('@logto/shared', () => ({
+mockEsm('@logto/shared', () => ({
   // eslint-disable-next-line unicorn/consistent-function-scoping
   buildIdGenerator: jest.fn(() => () => 'randomId'),
   buildApplicationSecret: jest.fn(() => 'randomId'),
 }));
 
+const { createRequester } = await import('#src/utils/test-utils.js');
+const applicationRoutes = await pickDefault(import('./application.js'));
+
 const customClientMetadata = {
   corsAllowedOrigins: ['http://localhost:5000', 'http://localhost:5001', 'https://silverhand.com'],
   idTokenTtl: 999_999,
   refreshTokenTtl: 100_000_000,
 };
 
-const customOidcClientMetadata = {
-  redirectUris: [],
-  postLogoutRedirectUris: [],
-};
-
 describe('application route', () => {
   const applicationRequest = createRequester({ authedRoutes: applicationRoutes });
 
diff --git a/packages/core/src/routes/authn.test.ts b/packages/core/src/routes/authn.test.ts
index 2993dd95a..7f54d02c3 100644
--- a/packages/core/src/routes/authn.test.ts
+++ b/packages/core/src/routes/authn.test.ts
@@ -1,11 +1,22 @@
+import { mockEsmWithActual, pickDefault } from '@logto/shared/esm';
+
 import RequestError from '#src/errors/RequestError/index.js';
-import * as functions from '#src/middleware/koa-auth.js';
 import { createRequester } from '#src/utils/test-utils.js';
 
-import authnRoutes from './authn.js';
+const { jest } = import.meta;
+
+const { verifyBearerTokenFromRequest } = await mockEsmWithActual(
+  '#src/middleware/koa-auth.js',
+  () => ({
+    verifyBearerTokenFromRequest: jest.fn(),
+  })
+);
+
+const request = createRequester({
+  anonymousRoutes: await pickDefault(import('#src/routes/authn.js')),
+});
 
 describe('authn route for Hasura', () => {
-  const request = createRequester({ anonymousRoutes: authnRoutes });
   const mockUserId = 'foo';
   const mockExpectedRole = 'some_role';
   const mockUnauthorizedRole = 'V';
@@ -17,7 +28,7 @@ describe('authn route for Hasura', () => {
 
   describe('with successful verification', () => {
     beforeEach(() => {
-      jest.spyOn(functions, 'verifyBearerTokenFromRequest').mockResolvedValue({
+      verifyBearerTokenFromRequest.mockResolvedValue({
         clientId: 'ok',
         sub: mockUserId,
         roleNames: [mockExpectedRole],
@@ -59,15 +70,13 @@ describe('authn route for Hasura', () => {
 
   describe('with failed verification', () => {
     beforeEach(() => {
-      jest
-        .spyOn(functions, 'verifyBearerTokenFromRequest')
-        .mockImplementation(async (_, resource) => {
-          if (resource) {
-            throw new RequestError({ code: 'auth.jwt_sub_missing', status: 401 });
-          }
+      verifyBearerTokenFromRequest.mockImplementation(async (_, resource) => {
+        if (resource) {
+          throw new RequestError({ code: 'auth.jwt_sub_missing', status: 401 });
+        }
 
-          return { clientId: 'not ok', sub: mockUserId };
-        });
+        return { clientId: 'not ok', sub: mockUserId };
+      });
     });
 
     it('throws 401 if no unauthorized role presents', async () => {
@@ -91,9 +100,10 @@ describe('authn route for Hasura', () => {
     });
 
     it('falls back to unauthorized role if JWT is invalid', async () => {
-      jest
-        .spyOn(functions, 'verifyBearerTokenFromRequest')
-        .mockRejectedValue(new RequestError({ code: 'auth.jwt_sub_missing', status: 401 }));
+      verifyBearerTokenFromRequest.mockRejectedValue(
+        new RequestError({ code: 'auth.jwt_sub_missing', status: 401 })
+      );
+
       const response = await request
         .get('/authn/hasura')
         .query({ resource: 'https://api.logto.io', unauthorizedRole: mockUnauthorizedRole });
diff --git a/packages/core/src/routes/connector.test.ts b/packages/core/src/routes/connector.test.ts
index 2b42e5681..f0fd3caa6 100644
--- a/packages/core/src/routes/connector.test.ts
+++ b/packages/core/src/routes/connector.test.ts
@@ -2,6 +2,7 @@
 import type { EmailConnector, SmsConnector } from '@logto/connector-kit';
 import { ConnectorPlatform, MessageTypes } from '@logto/connector-kit';
 import { ConnectorType } from '@logto/schemas';
+import { mockEsm, mockEsmWithActual, pickDefault } from '@logto/shared/esm';
 import { any } from 'zod';
 
 import {
@@ -16,36 +17,30 @@ import {
   mockLogtoConnector,
 } from '#src/__mocks__/index.js';
 import { defaultConnectorMethods } from '#src/connectors/consts.js';
-import type { ConnectorFactory, LogtoConnector } from '#src/connectors/types.js';
+import type { LogtoConnector } from '#src/connectors/types.js';
 import RequestError from '#src/errors/RequestError/index.js';
-import { removeUnavailableSocialConnectorTargets } from '#src/lib/sign-in-experience/index.js';
-import {
+import assertThat from '#src/utils/assert-that.js';
+import { createRequester } from '#src/utils/test-utils.js';
+
+const { jest } = import.meta;
+
+mockEsm('#src/lib/connector.js', () => ({
+  checkSocialConnectorTargetAndPlatformUniqueness: jest.fn(),
+}));
+
+const { removeUnavailableSocialConnectorTargets } = mockEsm(
+  '#src/lib/sign-in-experience/index.js',
+  () => ({
+    removeUnavailableSocialConnectorTargets: jest.fn(),
+  })
+);
+
+const {
   findConnectorById,
   countConnectorByConnectorId,
   deleteConnectorById,
   deleteConnectorByIds,
-} from '#src/queries/connector.js';
-import assertThat from '#src/utils/assert-that.js';
-import { createRequester } from '#src/utils/test-utils.js';
-
-import connectorRoutes from './connector.js';
-
-const loadConnectorFactoriesPlaceHolder = jest.fn() as jest.MockedFunction<
-  () => Promise<ConnectorFactory[]>
->;
-const getLogtoConnectorsPlaceHolder = jest.fn() as jest.MockedFunction<
-  () => Promise<LogtoConnector[]>
->;
-
-jest.mock('#src/lib/connector.js', () => ({
-  checkSocialConnectorTargetAndPlatformUniqueness: jest.fn(),
-}));
-
-jest.mock('#src/lib/sign-in-experience/index.js', () => ({
-  removeUnavailableSocialConnectorTargets: jest.fn(),
-}));
-
-jest.mock('#src/queries/connector.js', () => ({
+} = await mockEsmWithActual('#src/queries/connector.js', () => ({
   findConnectorById: jest.fn(),
   countConnectorByConnectorId: jest.fn(),
   deleteConnectorById: jest.fn(),
@@ -53,11 +48,13 @@ jest.mock('#src/queries/connector.js', () => ({
   insertConnector: jest.fn(async (body: unknown) => body),
 }));
 
-jest.mock('#src/connectors/index.js', () => ({
-  loadConnectorFactories: async () => loadConnectorFactoriesPlaceHolder(),
-  getLogtoConnectors: async () => getLogtoConnectorsPlaceHolder(),
+// eslint-disable-next-line @typescript-eslint/ban-types
+const getLogtoConnectors = jest.fn<Promise<LogtoConnector[]>, []>();
+const { loadConnectorFactories } = mockEsm('#src/connectors/index.js', () => ({
+  loadConnectorFactories: jest.fn(),
+  getLogtoConnectors,
   getLogtoConnectorById: async (connectorId: string) => {
-    const connectors = await getLogtoConnectorsPlaceHolder();
+    const connectors = await getLogtoConnectors();
     const connector = connectors.find(({ dbEntry }) => dbEntry.id === connectorId);
     assertThat(
       connector,
@@ -71,6 +68,7 @@ jest.mock('#src/connectors/index.js', () => ({
     return connector;
   },
 }));
+const connectorRoutes = await pickDefault(import('./connector.js'));
 
 describe('connector route', () => {
   const connectorRequest = createRequester({ authedRoutes: connectorRoutes });
@@ -81,13 +79,13 @@ describe('connector route', () => {
     });
 
     it('throws if more than one email connector exists', async () => {
-      getLogtoConnectorsPlaceHolder.mockResolvedValueOnce(mockLogtoConnectorList);
+      getLogtoConnectors.mockResolvedValueOnce(mockLogtoConnectorList);
       const response = await connectorRequest.get('/connectors').send({});
       expect(response).toHaveProperty('statusCode', 400);
     });
 
     it('throws if more than one SMS connector exists', async () => {
-      getLogtoConnectorsPlaceHolder.mockResolvedValueOnce(
+      getLogtoConnectors.mockResolvedValueOnce(
         mockLogtoConnectorList.filter((connector) => connector.type !== ConnectorType.Email)
       );
       const response = await connectorRequest.get('/connectors').send({});
@@ -95,7 +93,7 @@ describe('connector route', () => {
     });
 
     it('shows all connectors', async () => {
-      getLogtoConnectorsPlaceHolder.mockResolvedValueOnce(
+      getLogtoConnectors.mockResolvedValueOnce(
         mockLogtoConnectorList.filter((connector) => connector.type === ConnectorType.Social)
       );
       const response = await connectorRequest.get('/connectors').send({});
@@ -105,7 +103,7 @@ describe('connector route', () => {
 
   describe('GET /connector-factories', () => {
     it('show all connector factories', async () => {
-      (loadConnectorFactoriesPlaceHolder as jest.Mock).mockResolvedValueOnce([
+      loadConnectorFactories.mockResolvedValueOnce([
         { ...mockConnectorFactory, metadata: mockMetadata0, type: ConnectorType.Sms },
         { ...mockConnectorFactory, metadata: mockMetadata1, type: ConnectorType.Social },
         { ...mockConnectorFactory, metadata: mockMetadata2, type: ConnectorType.Email },
@@ -128,39 +126,38 @@ describe('connector route', () => {
     });
 
     it('throws when connector can not be found by given connectorId (locally)', async () => {
-      getLogtoConnectorsPlaceHolder.mockResolvedValueOnce(mockLogtoConnectorList.slice(2));
+      getLogtoConnectors.mockResolvedValueOnce(mockLogtoConnectorList.slice(2));
       const response = await connectorRequest.get('/connectors/findConnector').send({});
       expect(response).toHaveProperty('statusCode', 404);
     });
 
     it('throws when connector can not be found by given connectorId (remotely)', async () => {
-      getLogtoConnectorsPlaceHolder.mockResolvedValueOnce([]);
+      getLogtoConnectors.mockResolvedValueOnce([]);
       const response = await connectorRequest.get('/connectors/id0').send({});
       expect(response).toHaveProperty('statusCode', 404);
     });
 
     it('shows found connector information', async () => {
-      getLogtoConnectorsPlaceHolder.mockResolvedValueOnce(mockLogtoConnectorList);
+      getLogtoConnectors.mockResolvedValueOnce(mockLogtoConnectorList);
       const response = await connectorRequest.get('/connectors/id0').send({});
       expect(response).toHaveProperty('statusCode', 200);
     });
   });
 
   describe('POST /connectors', () => {
-    const mockedCountConnectorByConnectorId = countConnectorByConnectorId as jest.Mock;
     afterEach(() => {
       jest.clearAllMocks();
     });
 
     it('should post a new connector record', async () => {
-      loadConnectorFactoriesPlaceHolder.mockResolvedValueOnce([
+      loadConnectorFactories.mockResolvedValueOnce([
         {
           ...mockConnectorFactory,
           metadata: { ...mockConnectorFactory.metadata, id: 'connectorId' },
         },
       ]);
-      mockedCountConnectorByConnectorId.mockResolvedValueOnce({ count: 0 });
-      getLogtoConnectorsPlaceHolder.mockResolvedValueOnce([
+      countConnectorByConnectorId.mockResolvedValueOnce({ count: 0 });
+      getLogtoConnectors.mockResolvedValueOnce([
         {
           dbEntry: { ...mockConnector, connectorId: 'id0' },
           metadata: { ...mockMetadata, id: 'id0' },
@@ -185,13 +182,13 @@ describe('connector route', () => {
     });
 
     it('throws when connector factory not found', async () => {
-      loadConnectorFactoriesPlaceHolder.mockResolvedValueOnce([
+      loadConnectorFactories.mockResolvedValueOnce([
         {
           ...mockConnectorFactory,
           metadata: { ...mockConnectorFactory.metadata, id: 'connectorId' },
         },
       ]);
-      mockedCountConnectorByConnectorId.mockResolvedValueOnce({ count: 0 });
+      countConnectorByConnectorId.mockResolvedValueOnce({ count: 0 });
       const response = await connectorRequest.post('/connectors').send({
         connectorId: 'id0',
         config: { cliend_id: 'client_id', client_secret: 'client_secret' },
@@ -200,7 +197,7 @@ describe('connector route', () => {
     });
 
     it('should post a new record when add more than 1 instance with connector factory', async () => {
-      loadConnectorFactoriesPlaceHolder.mockResolvedValueOnce([
+      loadConnectorFactories.mockResolvedValueOnce([
         {
           ...mockConnectorFactory,
           metadata: {
@@ -211,8 +208,8 @@ describe('connector route', () => {
           },
         },
       ]);
-      mockedCountConnectorByConnectorId.mockResolvedValueOnce({ count: 1 });
-      getLogtoConnectorsPlaceHolder.mockResolvedValueOnce([
+      countConnectorByConnectorId.mockResolvedValueOnce({ count: 1 });
+      getLogtoConnectors.mockResolvedValueOnce([
         {
           dbEntry: { ...mockConnector, connectorId: 'id0' },
           metadata: { ...mockMetadata, id: 'id0', platform: ConnectorPlatform.Universal },
@@ -239,13 +236,13 @@ describe('connector route', () => {
     });
 
     it('throws when add more than 1 instance with non-connector factory', async () => {
-      loadConnectorFactoriesPlaceHolder.mockResolvedValueOnce([
+      loadConnectorFactories.mockResolvedValueOnce([
         {
           ...mockConnectorFactory,
           metadata: { ...mockConnectorFactory.metadata, id: 'id0' },
         },
       ]);
-      mockedCountConnectorByConnectorId.mockResolvedValueOnce({ count: 1 });
+      countConnectorByConnectorId.mockResolvedValueOnce({ count: 1 });
       const response = await connectorRequest.post('/connectors').send({
         connectorId: 'id0',
         config: { cliend_id: 'client_id', client_secret: 'client_secret' },
@@ -254,14 +251,14 @@ describe('connector route', () => {
     });
 
     it('should add a new record and delete old records with same connector type when add passwordless connectors', async () => {
-      loadConnectorFactoriesPlaceHolder.mockResolvedValueOnce([
+      loadConnectorFactories.mockResolvedValueOnce([
         {
           ...mockConnectorFactory,
           type: ConnectorType.Sms,
           metadata: { ...mockConnectorFactory.metadata, id: 'id0', isStandard: true },
         },
       ]);
-      getLogtoConnectorsPlaceHolder.mockResolvedValueOnce([
+      getLogtoConnectors.mockResolvedValueOnce([
         {
           dbEntry: { ...mockConnector, connectorId: 'id0' },
           metadata: { ...mockMetadata, id: 'id0' },
@@ -289,7 +286,7 @@ describe('connector route', () => {
     });
 
     it('throws when add more than 1 social connector instance with same target and platform (add from standard connector)', async () => {
-      loadConnectorFactoriesPlaceHolder.mockResolvedValueOnce([
+      loadConnectorFactories.mockResolvedValueOnce([
         {
           ...mockConnectorFactory,
           metadata: {
@@ -300,8 +297,8 @@ describe('connector route', () => {
           },
         },
       ]);
-      mockedCountConnectorByConnectorId.mockResolvedValueOnce({ count: 0 });
-      getLogtoConnectorsPlaceHolder.mockResolvedValueOnce([
+      countConnectorByConnectorId.mockResolvedValueOnce({ count: 0 });
+      getLogtoConnectors.mockResolvedValueOnce([
         {
           dbEntry: { ...mockConnector, connectorId: 'id0', metadata: { target: 'target' } },
           metadata: {
@@ -322,7 +319,7 @@ describe('connector route', () => {
     });
 
     it('throws when add more than 1 social connector instance with same target and platform (add social connector)', async () => {
-      loadConnectorFactoriesPlaceHolder.mockResolvedValueOnce([
+      loadConnectorFactories.mockResolvedValueOnce([
         {
           ...mockConnectorFactory,
           metadata: {
@@ -334,8 +331,8 @@ describe('connector route', () => {
           },
         },
       ]);
-      mockedCountConnectorByConnectorId.mockResolvedValueOnce({ count: 0 });
-      getLogtoConnectorsPlaceHolder.mockResolvedValueOnce([
+      countConnectorByConnectorId.mockResolvedValueOnce({ count: 0 });
+      getLogtoConnectors.mockResolvedValueOnce([
         {
           dbEntry: { ...mockConnector, connectorId: 'id0', metadata: { target: 'target' } },
           metadata: {
@@ -373,7 +370,7 @@ describe('connector route', () => {
         ...defaultConnectorMethods,
         sendMessage,
       };
-      getLogtoConnectorsPlaceHolder.mockResolvedValueOnce([mockedSmsConnector]);
+      getLogtoConnectors.mockResolvedValueOnce([mockedSmsConnector]);
       const response = await connectorRequest
         .post('/connectors/id/test')
         .send({ phone: '12345678901', config: { test: 123 } });
@@ -401,7 +398,7 @@ describe('connector route', () => {
         ...defaultConnectorMethods,
         sendMessage,
       };
-      getLogtoConnectorsPlaceHolder.mockResolvedValueOnce([mockedEmailConnector]);
+      getLogtoConnectors.mockResolvedValueOnce([mockedEmailConnector]);
       const response = await connectorRequest
         .post('/connectors/id/test')
         .send({ email: 'test@email.com', config: { test: 123 } });
@@ -425,7 +422,7 @@ describe('connector route', () => {
     });
 
     it('should throw when sms connector is not found', async () => {
-      getLogtoConnectorsPlaceHolder.mockResolvedValueOnce([]);
+      getLogtoConnectors.mockResolvedValueOnce([]);
       const response = await connectorRequest
         .post('/connectors/id/test')
         .send({ phone: '12345678901' });
@@ -433,7 +430,7 @@ describe('connector route', () => {
     });
 
     it('should throw when email connector is not found', async () => {
-      getLogtoConnectorsPlaceHolder.mockResolvedValueOnce([]);
+      getLogtoConnectors.mockResolvedValueOnce([]);
       const response = await connectorRequest
         .post('/connectors/id/test')
         .send({ email: 'test@email.com' });
@@ -445,21 +442,22 @@ describe('connector route', () => {
     beforeEach(() => {
       jest.resetAllMocks();
     });
+
     afterEach(() => {
       jest.clearAllMocks();
     });
 
     it('delete connector instance and remove unavailable social connector targets', async () => {
-      (findConnectorById as jest.Mock).mockResolvedValueOnce(mockConnector);
-      loadConnectorFactoriesPlaceHolder.mockResolvedValueOnce([mockConnectorFactory]);
+      findConnectorById.mockResolvedValueOnce(mockConnector);
+      loadConnectorFactories.mockResolvedValueOnce([mockConnectorFactory]);
       await connectorRequest.delete('/connectors/id').send({});
       expect(deleteConnectorById).toHaveBeenCalledTimes(1);
       expect(removeUnavailableSocialConnectorTargets).toHaveBeenCalledTimes(1);
     });
 
     it('delete connector instance (connector factory is not social type)', async () => {
-      (findConnectorById as jest.Mock).mockResolvedValueOnce(mockConnector);
-      loadConnectorFactoriesPlaceHolder.mockResolvedValueOnce([
+      findConnectorById.mockResolvedValueOnce(mockConnector);
+      loadConnectorFactories.mockResolvedValueOnce([
         { ...mockConnectorFactory, type: ConnectorType.Sms },
       ]);
       await connectorRequest.delete('/connectors/id').send({});
@@ -468,8 +466,8 @@ describe('connector route', () => {
     });
 
     it('delete connector instance (connector factory is not found)', async () => {
-      (findConnectorById as jest.Mock).mockResolvedValueOnce(mockConnector);
-      loadConnectorFactoriesPlaceHolder.mockResolvedValueOnce([]);
+      findConnectorById.mockResolvedValueOnce(mockConnector);
+      loadConnectorFactories.mockResolvedValueOnce([]);
       await connectorRequest.delete('/connectors/id').send({});
       expect(deleteConnectorById).toHaveBeenCalledTimes(1);
       expect(removeUnavailableSocialConnectorTargets).toHaveBeenCalledTimes(0);
@@ -477,7 +475,7 @@ describe('connector route', () => {
 
     it('throws when connector not exists with `id`', async () => {
       // eslint-disable-next-line unicorn/no-useless-undefined
-      (findConnectorById as jest.Mock).mockResolvedValueOnce(undefined);
+      findConnectorById.mockResolvedValueOnce(undefined);
       const response = await connectorRequest.delete('/connectors/id').send({});
       expect(response).toHaveProperty('statusCode', 500);
     });
diff --git a/packages/core/src/routes/connector.update.test.ts b/packages/core/src/routes/connector.update.test.ts
index 06eb2599e..b295476e5 100644
--- a/packages/core/src/routes/connector.update.test.ts
+++ b/packages/core/src/routes/connector.update.test.ts
@@ -1,5 +1,6 @@
 import { ConnectorError, ConnectorErrorCodes } from '@logto/connector-kit';
 import { ConnectorType } from '@logto/schemas';
+import { mockEsm, mockEsmWithActual, pickDefault } from '@logto/shared/esm';
 
 import {
   mockMetadata,
@@ -9,17 +10,14 @@ import {
 } from '#src/__mocks__/index.js';
 import type { LogtoConnector } from '#src/connectors/types.js';
 import RequestError from '#src/errors/RequestError/index.js';
-import { updateConnector } from '#src/queries/connector.js';
 import assertThat from '#src/utils/assert-that.js';
 import { createRequester } from '#src/utils/test-utils.js';
 
-import connectorRoutes from './connector.js';
+const { jest } = import.meta;
 
-const getLogtoConnectorsPlaceholder = jest.fn() as jest.MockedFunction<
-  () => Promise<LogtoConnector[]>
->;
-const getLogtoConnectorByIdPlaceholder = jest.fn(async (connectorId: string) => {
-  const connectors = await getLogtoConnectorsPlaceholder();
+const getLogtoConnectors = jest.fn() as jest.MockedFunction<() => Promise<LogtoConnector[]>>;
+const getLogtoConnectorById = jest.fn(async (connectorId: string) => {
+  const connectors = await getLogtoConnectors();
   const connector = connectors.find(({ dbEntry }) => dbEntry.id === connectorId);
 
   assertThat(
@@ -36,22 +34,25 @@ const getLogtoConnectorByIdPlaceholder = jest.fn(async (connectorId: string) =>
     sendMessage: sendMessagePlaceHolder,
   };
 }) as jest.MockedFunction<(connectorId: string) => Promise<LogtoConnector>>;
-const mockedUpdateConnector = updateConnector as jest.Mock;
+
 const sendMessagePlaceHolder = jest.fn();
 
-jest.mock('#src/queries/connector.js', () => ({
+const { updateConnector } = await mockEsmWithActual('#src/queries/connector.js', () => ({
   updateConnector: jest.fn(),
 }));
-jest.mock('#src/connectors.js', () => ({
-  getLogtoConnectors: async () => getLogtoConnectorsPlaceholder(),
-  getLogtoConnectorById: async (connectorId: string) =>
-    getLogtoConnectorByIdPlaceholder(connectorId),
+
+await mockEsmWithActual('#src/connectors.js', () => ({
+  getLogtoConnectors,
+  getLogtoConnectorById,
 }));
-jest.mock('#src/lib/sign-in-experience.js', () => ({
+
+mockEsm('#src/lib/sign-in-experience.js', () => ({
   // eslint-disable-next-line @typescript-eslint/no-empty-function
   removeUnavailableSocialConnectorTargets: async () => {},
 }));
 
+const connectorRoutes = await pickDefault(import('./connector.js'));
+
 describe('connector PATCH routes', () => {
   const connectorRequest = createRequester({ authedRoutes: connectorRoutes });
 
@@ -61,19 +62,19 @@ describe('connector PATCH routes', () => {
     });
 
     it('throws when connector can not be found by given connectorId (locally)', async () => {
-      getLogtoConnectorsPlaceholder.mockResolvedValueOnce(mockLogtoConnectorList.slice(0, 1));
+      getLogtoConnectors.mockResolvedValueOnce(mockLogtoConnectorList.slice(0, 1));
       const response = await connectorRequest.patch('/connectors/findConnector').send({});
       expect(response).toHaveProperty('statusCode', 404);
     });
 
     it('throws when connector can not be found by given connectorId (remotely)', async () => {
-      getLogtoConnectorsPlaceholder.mockResolvedValueOnce([]);
+      getLogtoConnectors.mockResolvedValueOnce([]);
       const response = await connectorRequest.patch('/connectors/id0').send({});
       expect(response).toHaveProperty('statusCode', 404);
     });
 
     it('config validation fails', async () => {
-      getLogtoConnectorsPlaceholder.mockResolvedValueOnce([
+      getLogtoConnectors.mockResolvedValueOnce([
         {
           dbEntry: mockConnector,
           metadata: mockMetadata,
@@ -91,7 +92,7 @@ describe('connector PATCH routes', () => {
     });
 
     it('throws when trying to update target', async () => {
-      getLogtoConnectorsPlaceholder.mockResolvedValue([
+      getLogtoConnectors.mockResolvedValue([
         {
           dbEntry: mockConnector,
           metadata: { ...mockMetadata, isStandard: true },
@@ -108,7 +109,7 @@ describe('connector PATCH routes', () => {
     });
 
     it('successfully updates connector configs', async () => {
-      getLogtoConnectorsPlaceholder.mockResolvedValue([
+      getLogtoConnectors.mockResolvedValue([
         {
           dbEntry: mockConnector,
           metadata: { ...mockMetadata, isStandard: true },
@@ -116,7 +117,7 @@ describe('connector PATCH routes', () => {
           ...mockLogtoConnector,
         },
       ]);
-      mockedUpdateConnector.mockResolvedValueOnce({
+      updateConnector.mockResolvedValueOnce({
         ...mockConnector,
         metadata: {
           target: 'target',
@@ -149,7 +150,7 @@ describe('connector PATCH routes', () => {
     });
 
     it('successfully clear connector config metadata', async () => {
-      getLogtoConnectorsPlaceholder.mockResolvedValueOnce([
+      getLogtoConnectors.mockResolvedValueOnce([
         {
           dbEntry: mockConnector,
           metadata: { ...mockMetadata, isStandard: true },
@@ -157,7 +158,7 @@ describe('connector PATCH routes', () => {
           ...mockLogtoConnector,
         },
       ]);
-      mockedUpdateConnector.mockResolvedValueOnce({
+      updateConnector.mockResolvedValueOnce({
         ...mockConnector,
         metadata: {
           target: '',
@@ -186,7 +187,7 @@ describe('connector PATCH routes', () => {
     });
 
     it('throws when set syncProfile to `true` and with non-social connector', async () => {
-      getLogtoConnectorsPlaceholder.mockResolvedValueOnce([
+      getLogtoConnectors.mockResolvedValueOnce([
         {
           dbEntry: mockConnector,
           metadata: mockMetadata,
@@ -200,7 +201,7 @@ describe('connector PATCH routes', () => {
     });
 
     it('successfully set syncProfile to `true` and with social connector', async () => {
-      getLogtoConnectorsPlaceholder.mockResolvedValue([
+      getLogtoConnectors.mockResolvedValue([
         {
           dbEntry: { ...mockConnector, syncProfile: false },
           metadata: mockMetadata,
@@ -220,7 +221,7 @@ describe('connector PATCH routes', () => {
     });
 
     it('successfully set syncProfile to `false`', async () => {
-      getLogtoConnectorsPlaceholder.mockResolvedValue([
+      getLogtoConnectors.mockResolvedValue([
         {
           dbEntry: { ...mockConnector, syncProfile: false },
           metadata: mockMetadata,
diff --git a/packages/core/src/routes/custom-phrase.test.ts b/packages/core/src/routes/custom-phrase.test.ts
index 72da14e2f..be0c35550 100644
--- a/packages/core/src/routes/custom-phrase.test.ts
+++ b/packages/core/src/routes/custom-phrase.test.ts
@@ -1,73 +1,66 @@
 import en from '@logto/phrases-ui/lib/locales/en.js';
-import type { CustomPhrase, SignInExperience, Translation } from '@logto/schemas';
+import type { CustomPhrase, SignInExperience } from '@logto/schemas';
+import { mockEsm, pickDefault } from '@logto/shared/esm';
 
 import { mockZhCnCustomPhrase, trTrTag, zhCnTag } from '#src/__mocks__/custom-phrase.js';
 import { mockSignInExperience } from '#src/__mocks__/index.js';
 import RequestError from '#src/errors/RequestError/index.js';
-import customPhraseRoutes from '#src/routes/custom-phrase.js';
 import { createRequester } from '#src/utils/test-utils.js';
 
+const { jest } = import.meta;
+
 const mockLanguageTag = zhCnTag;
 const mockPhrase = mockZhCnCustomPhrase;
 const mockCustomPhrases: Record<string, CustomPhrase> = {
   [mockLanguageTag]: mockPhrase,
 };
 
-const deleteCustomPhraseByLanguageTag = jest.fn(async (languageTag: string) => {
-  if (!mockCustomPhrases[languageTag]) {
-    throw new RequestError({ code: 'entity.not_found', status: 404 });
-  }
-});
+const {
+  deleteCustomPhraseByLanguageTag,
+  findAllCustomPhrases,
+  findCustomPhraseByLanguageTag,
+  upsertCustomPhrase,
+} = mockEsm('#src/queries/custom-phrase.js', () => ({
+  deleteCustomPhraseByLanguageTag: jest.fn(async (languageTag: string) => {
+    if (!mockCustomPhrases[languageTag]) {
+      throw new RequestError({ code: 'entity.not_found', status: 404 });
+    }
+  }),
+  findAllCustomPhrases: jest.fn(async (): Promise<CustomPhrase[]> => []),
+  findCustomPhraseByLanguageTag: jest.fn(async (languageTag: string) => {
+    const mockCustomPhrase = mockCustomPhrases[languageTag];
 
-const findCustomPhraseByLanguageTag = jest.fn(async (languageTag: string) => {
-  const mockCustomPhrase = mockCustomPhrases[languageTag];
+    if (!mockCustomPhrase) {
+      throw new RequestError({ code: 'entity.not_found', status: 404 });
+    }
 
-  if (!mockCustomPhrase) {
-    throw new RequestError({ code: 'entity.not_found', status: 404 });
-  }
-
-  return mockCustomPhrase;
-});
-
-const findAllCustomPhrases = jest.fn(async (): Promise<CustomPhrase[]> => []);
-
-const upsertCustomPhrase = jest.fn(async (customPhrase: CustomPhrase) => mockPhrase);
-
-jest.mock('#src/queries/custom-phrase.js', () => ({
-  deleteCustomPhraseByLanguageTag: async (tag: string) => deleteCustomPhraseByLanguageTag(tag),
-  findAllCustomPhrases: async () => findAllCustomPhrases(),
-  findCustomPhraseByLanguageTag: async (tag: string) => findCustomPhraseByLanguageTag(tag),
-  upsertCustomPhrase: async (customPhrase: CustomPhrase) => upsertCustomPhrase(customPhrase),
+    return mockCustomPhrase;
+  }),
+  upsertCustomPhrase: jest.fn(async () => mockPhrase),
 }));
 
-const isStrictlyPartial = jest.fn(
-  (fullTranslation: Translation, partialTranslation: Partial<Translation>) => true
-);
-
-jest.mock('#src/utils/translation.js', () => ({
-  isStrictlyPartial: (fullTranslation: Translation, partialTranslation: Translation) =>
-    isStrictlyPartial(fullTranslation, partialTranslation),
+const { isStrictlyPartial } = mockEsm('#src/utils/translation.js', () => ({
+  isStrictlyPartial: jest.fn(() => true),
 }));
 
 const mockFallbackLanguage = trTrTag;
 
-const findDefaultSignInExperience = jest.fn(
-  async (): Promise<SignInExperience> => ({
-    ...mockSignInExperience,
-    languageInfo: {
-      autoDetect: true,
-      fallbackLanguage: mockFallbackLanguage,
-    },
-  })
-);
-
-jest.mock('#src/queries/sign-in-experience.js', () => ({
-  findDefaultSignInExperience: async () => findDefaultSignInExperience(),
+mockEsm('#src/queries/sign-in-experience.js', () => ({
+  findDefaultSignInExperience: jest.fn(
+    async (): Promise<SignInExperience> => ({
+      ...mockSignInExperience,
+      languageInfo: {
+        autoDetect: true,
+        fallbackLanguage: mockFallbackLanguage,
+      },
+    })
+  ),
 }));
 
-describe('customPhraseRoutes', () => {
-  const customPhraseRequest = createRequester({ authedRoutes: customPhraseRoutes });
+const customPhraseRoutes = await pickDefault(import('./custom-phrase.js'));
+const customPhraseRequest = createRequester({ authedRoutes: customPhraseRoutes });
 
+describe('customPhraseRoutes', () => {
   afterEach(() => {
     jest.clearAllMocks();
   });
diff --git a/packages/core/src/routes/dashboard.test.ts b/packages/core/src/routes/dashboard.test.ts
index 386847384..e8766c7de 100644
--- a/packages/core/src/routes/dashboard.test.ts
+++ b/packages/core/src/routes/dashboard.test.ts
@@ -1,25 +1,20 @@
 // The FP version works better for `format()`
 /* eslint-disable import/no-duplicates */
+import { mockEsm, pickDefault } from '@logto/shared/esm';
 import { endOfDay, subDays } from 'date-fns';
 import { format } from 'date-fns/fp';
+
+import { createRequester } from '#src/utils/test-utils.js';
 /* eslint-enable import/no-duplicates */
 
-import dashboardRoutes from '#src/routes/dashboard.js';
-import { createRequester } from '#src/utils/test-utils.js';
+const { jest } = import.meta;
 
 const totalUserCount = 1000;
-const countUsers = jest.fn(async () => ({ count: totalUserCount }));
-const getDailyNewUserCountsByTimeInterval = jest.fn(
-  async (startTimeExclusive: number, endTimeInclusive: number) => mockDailyNewUserCounts
-);
 const formatToQueryDate = format('yyyy-MM-dd');
 
-jest.mock('#src/queries/user.js', () => ({
-  countUsers: async () => countUsers(),
-  getDailyNewUserCountsByTimeInterval: async (
-    startTimeExclusive: number,
-    endTimeInclusive: number
-  ) => getDailyNewUserCountsByTimeInterval(startTimeExclusive, endTimeInclusive),
+const { countUsers, getDailyNewUserCountsByTimeInterval } = mockEsm('#src/queries/user.js', () => ({
+  countUsers: jest.fn(async () => ({ count: totalUserCount })),
+  getDailyNewUserCountsByTimeInterval: jest.fn(async () => mockDailyNewUserCounts),
 }));
 
 const mockDailyNewUserCounts = [
@@ -44,21 +39,14 @@ const mockDailyActiveUserCounts = [
 
 const mockActiveUserCount = 1000;
 
-const getDailyActiveUserCountsByTimeInterval = jest.fn(
-  async (startTimeExclusive: number, endTimeInclusive: number) => mockDailyActiveUserCounts
+const { getDailyActiveUserCountsByTimeInterval, countActiveUsersByTimeInterval } = mockEsm(
+  '#src/queries/log.js',
+  () => ({
+    getDailyActiveUserCountsByTimeInterval: jest.fn().mockResolvedValue(mockDailyActiveUserCounts),
+    countActiveUsersByTimeInterval: jest.fn().mockResolvedValue({ count: mockActiveUserCount }),
+  })
 );
-const countActiveUsersByTimeInterval = jest.fn(
-  async (startTimeExclusive: number, endTimeInclusive: number) => ({ count: mockActiveUserCount })
-);
-
-jest.mock('#src/queries/log.js', () => ({
-  getDailyActiveUserCountsByTimeInterval: async (
-    startTimeExclusive: number,
-    endTimeInclusive: number
-  ) => getDailyActiveUserCountsByTimeInterval(startTimeExclusive, endTimeInclusive),
-  countActiveUsersByTimeInterval: async (startTimeExclusive: number, endTimeInclusive: number) =>
-    countActiveUsersByTimeInterval(startTimeExclusive, endTimeInclusive),
-}));
+const dashboardRoutes = await pickDefault(import('./dashboard.js'));
 
 describe('dashboardRoutes', () => {
   const logRequest = createRequester({ authedRoutes: dashboardRoutes });
diff --git a/packages/core/src/routes/interaction/actions/submit-interaction.test.ts b/packages/core/src/routes/interaction/actions/submit-interaction.test.ts
index 168e4196c..878e7c815 100644
--- a/packages/core/src/routes/interaction/actions/submit-interaction.test.ts
+++ b/packages/core/src/routes/interaction/actions/submit-interaction.test.ts
@@ -1,10 +1,7 @@
 import { Event } from '@logto/schemas';
-import { Provider } from 'oidc-provider';
+import { mockEsm, pickDefault } from '@logto/shared/esm';
 
-import { getLogtoConnectorById } from '#src/connectors/index.js';
-import { assignInteractionResults } from '#src/lib/session.js';
-import { encryptUserPassword, generateUserId, insertUser } from '#src/lib/user.js';
-import { updateUserById } from '#src/queries/user.js';
+import { createMockProvider } from '#src/test-utils/oidc-provider.js';
 import { createContextWithRouteParameters } from '#src/utils/test-utils.js';
 
 import type {
@@ -14,19 +11,20 @@ import type {
   VerifiedSignInInteractionResult,
   VerifiedForgotPasswordInteractionResult,
 } from '../types/index.js';
-import submitInteraction from './submit-interaction.js';
 
-jest.mock('#src/connectors/index.js', () => ({
+const { jest } = import.meta;
+
+const { getLogtoConnectorById } = mockEsm('#src/connectors/index.js', () => ({
   getLogtoConnectorById: jest
     .fn()
     .mockResolvedValue({ metadata: { target: 'logto' }, dbEntry: { syncProfile: true } }),
 }));
 
-jest.mock('#src/lib/session.js', () => ({
+const { assignInteractionResults } = mockEsm('#src/lib/session.js', () => ({
   assignInteractionResults: jest.fn(),
 }));
 
-jest.mock('#src/lib/user.js', () => ({
+const { encryptUserPassword, generateUserId, insertUser } = mockEsm('#src/lib/user.js', () => ({
   encryptUserPassword: jest.fn().mockResolvedValue({
     passwordEncrypted: 'passwordEncrypted',
     passwordEncryptionMethod: 'plain',
@@ -35,25 +33,21 @@ jest.mock('#src/lib/user.js', () => ({
   insertUser: jest.fn(),
 }));
 
-jest.mock('#src/queries/user.js', () => ({
+mockEsm('#src/queries/user.js', () => ({
   findUserById: jest
     .fn()
     .mockResolvedValue({ identities: { google: { userId: 'googleId', details: {} } } }),
   updateUserById: jest.fn(),
 }));
 
-jest.mock('oidc-provider', () => ({
-  Provider: jest.fn(() => ({
-    interactionDetails: jest.fn(async () => ({ params: {}, jti: 'jti' })),
-  })),
-}));
-
+const { updateUserById } = await import('#src/queries/user.js');
+const submitInteraction = await pickDefault(import('./submit-interaction.js'));
 const now = Date.now();
 
 jest.useFakeTimers().setSystemTime(now);
 
 describe('submit action', () => {
-  const provider = new Provider('');
+  const provider = createMockProvider();
   const log = jest.fn();
   const ctx: InteractionContext = {
     ...createContextWithRouteParameters(),
@@ -116,7 +110,7 @@ describe('submit action', () => {
   });
 
   it('sign-in', async () => {
-    (getLogtoConnectorById as jest.Mock).mockResolvedValueOnce({
+    getLogtoConnectorById.mockResolvedValueOnce({
       metadata: { target: 'logto' },
       dbEntry: { syncProfile: false },
     });
diff --git a/packages/core/src/routes/interaction/index.test.ts b/packages/core/src/routes/interaction/index.test.ts
index af08a4710..7751beda7 100644
--- a/packages/core/src/routes/interaction/index.test.ts
+++ b/packages/core/src/routes/interaction/index.test.ts
@@ -1,21 +1,16 @@
 import { ConnectorType } from '@logto/connector-kit';
 import { Event } from '@logto/schemas';
-import { Provider } from 'oidc-provider';
+import { demoAppApplicationId } from '@logto/schemas/lib/seeds/application.js';
+import { mockEsm, mockEsmDefault, mockEsmWithActual, pickDefault } from '@logto/shared/esm';
 
 import { mockSignInExperience } from '#src/__mocks__/sign-in-experience.js';
 import RequestError from '#src/errors/RequestError/index.js';
+import { createMockProvider } from '#src/test-utils/oidc-provider.js';
 import { createRequester } from '#src/utils/test-utils.js';
 
-import submitInteraction from './actions/submit-interaction.js';
-import interactionRoutes, { verificationPrefix, interactionPrefix } from './index.js';
 import type { InteractionContext } from './types/index.js';
-import { getInteractionStorage } from './utils/interaction.js';
-import { sendPasscodeToIdentifier } from './utils/passcode-validation.js';
-import {
-  verifyIdentifier,
-  verifyProfile,
-  validateMandatoryUserProfile,
-} from './verifications/index.js';
+
+const { jest } = import.meta;
 
 // FIXME @Darcy: no more `enabled` for `connectors` table
 const getLogtoConnectorByIdHelper = jest.fn(async (connectorId: string) => {
@@ -36,7 +31,7 @@ const getLogtoConnectorByIdHelper = jest.fn(async (connectorId: string) => {
   };
 });
 
-jest.mock('#src/connectors.js', () => ({
+await mockEsmWithActual('#src/connectors/index.js', () => ({
   getLogtoConnectorById: jest.fn(async (connectorId: string) => {
     const connector = await getLogtoConnectorByIdHelper(connectorId);
 
@@ -51,57 +46,67 @@ jest.mock('#src/connectors.js', () => ({
   }),
 }));
 
-jest.mock('./utils/passcode-validation.js', () => ({
-  sendPasscodeToIdentifier: jest.fn(),
-}));
-
-jest.mock('oidc-provider', () => ({
-  Provider: jest.fn(() => ({
-    interactionDetails: jest.fn().mockResolvedValue({
-      jti: 'jti',
-      result: {},
-      params: {
-        client_id: 'demo_app',
-      },
-    }),
-  })),
-}));
-
-jest.mock('#src/lib/sign-in-experience/index.js', () => ({
-  getSignInExperienceForApplication: jest.fn().mockResolvedValue(mockSignInExperience),
-}));
-
-jest.mock('./verifications/index.js', () => ({
-  verifyIdentifier: jest.fn(),
-  verifyProfile: jest.fn(),
-  validateMandatoryUserProfile: jest.fn(),
-}));
-
-jest.mock('./actions/submit-interaction.js', () =>
-  jest.fn((_interaction, ctx: InteractionContext) => {
-    ctx.body = { redirectUri: 'logto.io' };
+const { sendPasscodeToIdentifier } = await mockEsmWithActual(
+  './utils/passcode-validation.js',
+  () => ({
+    sendPasscodeToIdentifier: jest.fn(),
   })
 );
 
-jest.mock('./utils/interaction.js', () => ({
+mockEsm('#src/lib/sign-in-experience/index.js', () => ({
+  getSignInExperienceForApplication: jest.fn().mockResolvedValue(mockSignInExperience),
+}));
+
+const { verifyIdentifier, verifyProfile, validateMandatoryUserProfile } = mockEsm(
+  './verifications/index.js',
+  () => ({
+    verifyIdentifier: jest.fn(),
+    verifyProfile: jest.fn(),
+    validateMandatoryUserProfile: jest.fn(),
+  })
+);
+
+const { default: submitInteraction } = mockEsm('./actions/submit-interaction.js', () => ({
+  default: jest.fn((_interaction, ctx: InteractionContext) => {
+    ctx.body = { redirectUri: 'logto.io' };
+  }),
+}));
+
+const { getInteractionStorage } = mockEsm('./utils/interaction.js', () => ({
   getInteractionStorage: jest.fn(),
 }));
 
 const log = jest.fn();
 
-const koaInteractionBodyGuardSpy = jest.spyOn(
-  jest.requireActual('./middleware/koa-interaction-body-guard.js'),
-  'default'
+const koaInteractionBodyGuard = await pickDefault(
+  import('./middleware/koa-interaction-body-guard.js')
 );
-const koaSessionSignInExperienceGuardSpy = jest.spyOn(
-  jest.requireActual('./middleware/koa-session-sign-in-experience-guard.js'),
-  'default'
+const koaSessionSignInExperienceGuard = await pickDefault(
+  import('./middleware/koa-session-sign-in-experience-guard.js')
 );
 
+const koaInteractionBodyGuardSpy = mockEsmDefault(
+  './middleware/koa-interaction-body-guard.js',
+  () => jest.fn(koaInteractionBodyGuard)
+);
+
+const koaSessionSignInExperienceGuardSpy = mockEsmDefault(
+  './middleware/koa-session-sign-in-experience-guard.js',
+  () => jest.fn(koaSessionSignInExperienceGuard)
+);
+
+const {
+  default: interactionRoutes,
+  verificationPrefix,
+  interactionPrefix,
+} = await import('./index.js');
+
 describe('session -> interactionRoutes', () => {
   const sessionRequest = createRequester({
     anonymousRoutes: interactionRoutes,
-    provider: new Provider(''),
+    provider: createMockProvider(
+      jest.fn().mockResolvedValue({ params: {}, jti: 'jti', client_id: demoAppApplicationId })
+    ),
     middlewares: [
       async (ctx, next) => {
         ctx.addLogContext = jest.fn();
@@ -162,14 +167,13 @@ describe('session -> interactionRoutes', () => {
 
   describe('PATCH /interaction', () => {
     const path = interactionPrefix;
-    const getInteractionStorageMock = getInteractionStorage as jest.Mock;
 
     afterEach(() => {
       jest.clearAllMocks();
     });
 
     it('sign-in event with register event interaction session in record should call methods properly', async () => {
-      getInteractionStorageMock.mockResolvedValueOnce({ event: Event.Register });
+      getInteractionStorage.mockResolvedValueOnce({ event: Event.Register });
 
       const body = {
         event: Event.SignIn,
@@ -185,7 +189,7 @@ describe('session -> interactionRoutes', () => {
     });
 
     it('sign-in event with forgot password event interaction session in record should reject', async () => {
-      getInteractionStorageMock.mockResolvedValueOnce({ event: Event.ForgotPassword });
+      getInteractionStorage.mockResolvedValueOnce({ event: Event.ForgotPassword });
 
       const body = {
         event: Event.SignIn,
@@ -200,7 +204,7 @@ describe('session -> interactionRoutes', () => {
     });
 
     it('Forgot event with forgot password event interaction session in record should call methods properly', async () => {
-      getInteractionStorageMock.mockResolvedValueOnce({ event: Event.ForgotPassword });
+      getInteractionStorage.mockResolvedValueOnce({ event: Event.ForgotPassword });
 
       const body = {
         event: Event.ForgotPassword,
@@ -216,7 +220,7 @@ describe('session -> interactionRoutes', () => {
     });
 
     it('Forgot event with sign-in event interaction session in record should call methods properly', async () => {
-      getInteractionStorageMock.mockResolvedValueOnce({ event: Event.SignIn });
+      getInteractionStorage.mockResolvedValueOnce({ event: Event.SignIn });
 
       const body = {
         event: Event.ForgotPassword,
diff --git a/packages/core/src/routes/interaction/middleware/koa-interaction-body-guard.test.ts b/packages/core/src/routes/interaction/middleware/koa-interaction-body-guard.test.ts
index 2fe0bc0d9..1be87b4cc 100644
--- a/packages/core/src/routes/interaction/middleware/koa-interaction-body-guard.test.ts
+++ b/packages/core/src/routes/interaction/middleware/koa-interaction-body-guard.test.ts
@@ -1,20 +1,17 @@
 import { Event } from '@logto/schemas';
+import { mockEsmDefault, pickDefault } from '@logto/shared/esm';
 import type { Context } from 'koa';
 
 import { interactionMocks } from '#src/__mocks__/interactions.js';
 import { emptyMiddleware, createContextWithRouteParameters } from '#src/utils/test-utils.js';
 
 import type { WithGuardedIdentifierPayloadContext } from './koa-interaction-body-guard.js';
-import koaInteractionBodyGuard from './koa-interaction-body-guard.js';
 
-jest.mock('koa-body', () => emptyMiddleware);
+const { jest } = import.meta;
 
-// User this to bypass the context type assertion
-const mockIdentifierPayload = Object.freeze({
-  type: 'username_password',
-  username: 'username',
-  password: 'password',
-});
+mockEsmDefault('koa-body', () => emptyMiddleware);
+
+const koaInteractionBodyGuard = await pickDefault(import('./koa-interaction-body-guard.js'));
 
 describe('koaInteractionBodyGuard', () => {
   const baseCtx = createContextWithRouteParameters();
diff --git a/packages/core/src/routes/interaction/middleware/koa-session-sign-inexperience-guard.test.ts b/packages/core/src/routes/interaction/middleware/koa-session-sign-inexperience-guard.test.ts
index 119a163a1..3633e81a8 100644
--- a/packages/core/src/routes/interaction/middleware/koa-session-sign-inexperience-guard.test.ts
+++ b/packages/core/src/routes/interaction/middleware/koa-session-sign-inexperience-guard.test.ts
@@ -1,31 +1,27 @@
 import { Event } from '@logto/schemas';
-import { Provider } from 'oidc-provider';
+import { mockEsm, pickDefault } from '@logto/shared/esm';
 
 import { mockSignInExperience } from '#src/__mocks__/sign-in-experience.js';
+import { createMockProvider } from '#src/test-utils/oidc-provider.js';
 import { createContextWithRouteParameters } from '#src/utils/test-utils.js';
 
-import {
-  signInModeValidation,
-  identifierValidation,
-  profileValidation,
-} from '../utils/sign-in-experience-validation.js';
-import koaSessionSignInExperienceGuard from './koa-session-sign-in-experience-guard.js';
+const { jest } = import.meta;
 
-jest.mock('#src/lib/sign-in-experience/index.js', () => ({
+mockEsm('#src/lib/sign-in-experience/index.js', () => ({
   getSignInExperienceForApplication: jest.fn().mockResolvedValue(mockSignInExperience),
 }));
 
-jest.mock('../utils/sign-in-experience-validation.js', () => ({
+const mockUtils = {
   signInModeValidation: jest.fn(),
   identifierValidation: jest.fn(),
   profileValidation: jest.fn(),
-}));
+};
 
-jest.mock('oidc-provider', () => ({
-  Provider: jest.fn(() => ({
-    interactionDetails: jest.fn(async () => ({ params: {} })),
-  })),
-}));
+mockEsm('../utils/sign-in-experience-validation.js', () => mockUtils);
+
+const koaSessionSignInExperienceGuard = await pickDefault(
+  import('./koa-session-sign-in-experience-guard.js')
+);
 
 describe('koaSessionSignInExperienceGuard', () => {
   const baseCtx = createContextWithRouteParameters();
@@ -41,14 +37,15 @@ describe('koaSessionSignInExperienceGuard', () => {
       }),
       signInExperience: mockSignInExperience,
     };
+    const provider = createMockProvider();
 
-    await koaSessionSignInExperienceGuard(new Provider(''))(ctx, next);
+    await koaSessionSignInExperienceGuard(provider)(ctx, next);
 
-    expect(signInModeValidation).toBeCalledWith(Event.SignIn, mockSignInExperience);
-    expect(identifierValidation).toBeCalledWith(
+    expect(mockUtils.signInModeValidation).toBeCalledWith(Event.SignIn, mockSignInExperience);
+    expect(mockUtils.identifierValidation).toBeCalledWith(
       { username: 'username', password: 'password' },
       mockSignInExperience
     );
-    expect(profileValidation).toBeCalledWith({ email: 'email' }, mockSignInExperience);
+    expect(mockUtils.profileValidation).toBeCalledWith({ email: 'email' }, mockSignInExperience);
   });
 });
diff --git a/packages/core/src/routes/interaction/utils/find-user-by-identifier.test.ts b/packages/core/src/routes/interaction/utils/find-user-by-identifier.test.ts
index f24f3f837..3ac8af3a6 100644
--- a/packages/core/src/routes/interaction/utils/find-user-by-identifier.test.ts
+++ b/packages/core/src/routes/interaction/utils/find-user-by-identifier.test.ts
@@ -1,43 +1,41 @@
-import { getLogtoConnectorById } from '#src/connectors/index.js';
-import {
-  findUserByEmail,
-  findUserByUsername,
-  findUserByPhone,
-  findUserByIdentity,
-} from '#src/queries/user.js';
+import { mockEsm, pickDefault } from '@logto/shared/esm';
 
-import findUserByIdentifier from './find-user-by-identifier.js';
+const { jest } = import.meta;
 
-jest.mock('#src/queries/user.js', () => ({
+const queries = {
   findUserByEmail: jest.fn(),
   findUserByUsername: jest.fn(),
   findUserByPhone: jest.fn(),
   findUserByIdentity: jest.fn(),
-}));
+};
 
-jest.mock('#src/connectors/index.js', () => ({
+mockEsm('#src/queries/user.js', () => queries);
+
+const { getLogtoConnectorById } = mockEsm('#src/connectors/index.js', () => ({
   getLogtoConnectorById: jest.fn().mockResolvedValue({ metadata: { target: 'logto' } }),
 }));
 
+const findUserByIdentifier = await pickDefault(import('./find-user-by-identifier.js'));
+
 describe('findUserByIdentifier', () => {
   it('username', async () => {
     await findUserByIdentifier({ username: 'foo' });
-    expect(findUserByUsername).toBeCalledWith('foo');
+    expect(queries.findUserByUsername).toBeCalledWith('foo');
   });
 
   it('email', async () => {
     await findUserByIdentifier({ email: 'foo@logto.io' });
-    expect(findUserByEmail).toBeCalledWith('foo@logto.io');
+    expect(queries.findUserByEmail).toBeCalledWith('foo@logto.io');
   });
 
   it('phone', async () => {
     await findUserByIdentifier({ phone: '123456' });
-    expect(findUserByPhone).toBeCalledWith('123456');
+    expect(queries.findUserByPhone).toBeCalledWith('123456');
   });
 
   it('social', async () => {
     await findUserByIdentifier({ connectorId: 'connector', userInfo: { id: 'foo' } });
     expect(getLogtoConnectorById).toBeCalledWith('connector');
-    expect(findUserByIdentity).toBeCalledWith('logto', 'foo');
+    expect(queries.findUserByIdentity).toBeCalledWith('logto', 'foo');
   });
 });
diff --git a/packages/core/src/routes/interaction/utils/passcode-validation.test.ts b/packages/core/src/routes/interaction/utils/passcode-validation.test.ts
index e8c5d27f2..3c2f9c51d 100644
--- a/packages/core/src/routes/interaction/utils/passcode-validation.test.ts
+++ b/packages/core/src/routes/interaction/utils/passcode-validation.test.ts
@@ -1,14 +1,17 @@
 import { PasscodeType, Event } from '@logto/schemas';
-
-import { createPasscode, sendPasscode } from '#src/lib/passcode.js';
+import { mockEsmWithActual } from '@logto/shared/esm';
 
 import type { SendPasscodePayload } from '../types/index.js';
-import { sendPasscodeToIdentifier } from './passcode-validation.js';
 
-jest.mock('#src/lib/passcode.js', () => ({
+const { jest } = import.meta;
+const passcode = {
   createPasscode: jest.fn(() => ({})),
   sendPasscode: jest.fn().mockResolvedValue({ dbEntry: { id: 'foo' } }),
-}));
+};
+
+await mockEsmWithActual('#src/lib/passcode.js', () => passcode);
+
+const { sendPasscodeToIdentifier } = await import('./passcode-validation.js');
 
 const sendPasscodeTestCase = [
   {
@@ -38,8 +41,6 @@ const sendPasscodeTestCase = [
 ];
 
 describe('passcode-validation utils', () => {
-  const createPasscodeMock = createPasscode as jest.Mock;
-  const sendPasscodeMock = sendPasscode as jest.Mock;
   const log = jest.fn();
 
   afterEach(() => {
@@ -50,8 +51,8 @@ describe('passcode-validation utils', () => {
     'send passcode successfully',
     async ({ payload, createPasscodeParams }) => {
       await sendPasscodeToIdentifier(payload as SendPasscodePayload, 'jti', log);
-      expect(createPasscodeMock).toBeCalledWith('jti', ...createPasscodeParams);
-      expect(sendPasscodeMock).toBeCalled();
+      expect(passcode.createPasscode).toBeCalledWith('jti', ...createPasscodeParams);
+      expect(passcode.sendPasscode).toBeCalled();
     }
   );
 });
diff --git a/packages/core/src/routes/interaction/utils/social-verification.test.ts b/packages/core/src/routes/interaction/utils/social-verification.test.ts
index 58181c06b..42a2ce18d 100644
--- a/packages/core/src/routes/interaction/utils/social-verification.test.ts
+++ b/packages/core/src/routes/interaction/utils/social-verification.test.ts
@@ -1,14 +1,13 @@
 import { ConnectorType } from '@logto/connector-kit';
+import { mockEsm } from '@logto/shared/esm';
 
-import { getUserInfoByAuthCode } from '#src/lib/social.js';
+const { jest } = import.meta;
 
-import { verifySocialIdentity } from './social-verification.js';
-
-jest.mock('#src/lib/social.js', () => ({
+const { getUserInfoByAuthCode } = mockEsm('#src/lib/social.js', () => ({
   getUserInfoByAuthCode: jest.fn().mockResolvedValue({ id: 'foo' }),
 }));
 
-jest.mock('#src/connectors.js', () => ({
+mockEsm('#src/connectors.js', () => ({
   getLogtoConnectorById: jest.fn().mockResolvedValue({
     metadata: {
       id: 'social',
@@ -18,6 +17,7 @@ jest.mock('#src/connectors.js', () => ({
   }),
 }));
 
+const { verifySocialIdentity } = await import('./social-verification.js');
 const log = jest.fn();
 
 describe('social-verification', () => {
diff --git a/packages/core/src/routes/interaction/verifications/identifier-payload-verification.test.ts b/packages/core/src/routes/interaction/verifications/identifier-payload-verification.test.ts
index c515f4f4e..5c25c59f3 100644
--- a/packages/core/src/routes/interaction/verifications/identifier-payload-verification.test.ts
+++ b/packages/core/src/routes/interaction/verifications/identifier-payload-verification.test.ts
@@ -1,55 +1,47 @@
 import { Event } from '@logto/schemas';
-import { Provider } from 'oidc-provider';
+import { mockEsm, mockEsmDefault, mockEsmWithActual, pickDefault } from '@logto/shared/esm';
 
 import RequestError from '#src/errors/RequestError/index.js';
-import { verifyUserPassword } from '#src/lib/user.js';
+import { createMockProvider } from '#src/test-utils/oidc-provider.js';
 import { createContextWithRouteParameters } from '#src/utils/test-utils.js';
 
 import type { AnonymousInteractionResult, VerifiedPhoneIdentifier } from '../types/index.js';
-import findUserByIdentifier from '../utils/find-user-by-identifier.js';
-import { verifyIdentifierByPasscode } from '../utils/passcode-validation.js';
-import { verifySocialIdentity } from '../utils/social-verification.js';
-import identifierPayloadVerification from './identifier-payload-verification.js';
 
-jest.mock('#src/lib/user.js', () => ({
+const { jest } = import.meta;
+
+const { verifyUserPassword } = mockEsm('#src/lib/user.js', () => ({
   verifyUserPassword: jest.fn(),
 }));
 
-jest.mock('../utils/find-user-by-identifier.js', () => jest.fn());
+const findUserByIdentifier = mockEsmDefault('../utils/find-user-by-identifier.js', () => jest.fn());
 
-jest.mock('../utils/interaction.js', () => ({
-  ...jest.requireActual('../utils/interaction.js'),
+await mockEsmWithActual('../utils/interaction.js', () => ({
   storeInteractionResult: jest.fn(),
 }));
 
-jest.mock('../utils/passcode-validation.js', () => ({
+const { verifyIdentifierByPasscode } = mockEsm('../utils/passcode-validation.js', () => ({
   verifyIdentifierByPasscode: jest.fn(),
 }));
 
-jest.mock('oidc-provider', () => ({
-  Provider: jest.fn(() => ({
-    interactionDetails: jest.fn(async () => ({ params: {}, jti: 'jti' })),
-  })),
-}));
-
-jest.mock('../utils/social-verification.js', () => ({
+const { verifySocialIdentity } = mockEsm('../utils/social-verification.js', () => ({
   verifySocialIdentity: jest.fn().mockResolvedValue({ id: 'foo' }),
 }));
 
+const identifierPayloadVerification = await pickDefault(
+  import('./identifier-payload-verification.js')
+);
+
 const log = jest.fn();
 
 describe('identifier verification', () => {
   const baseCtx = { ...createContextWithRouteParameters(), log };
-  const verifyUserPasswordMock = verifyUserPassword as jest.Mock;
-  const findUserByIdentifierMock = findUserByIdentifier as jest.Mock;
-  const verifyIdentifierByPasscodeMock = verifyIdentifierByPasscode as jest.Mock;
 
   afterEach(() => {
     jest.clearAllMocks();
   });
 
   it('username password user not found', async () => {
-    findUserByIdentifierMock.mockResolvedValueOnce(null);
+    findUserByIdentifier.mockResolvedValueOnce(null);
 
     const identifier = {
       username: 'username',
@@ -64,14 +56,14 @@ describe('identifier verification', () => {
       }),
     };
 
-    await expect(identifierPayloadVerification(ctx, new Provider(''))).rejects.toThrow();
+    await expect(identifierPayloadVerification(ctx, createMockProvider())).rejects.toThrow();
     expect(findUserByIdentifier).toBeCalledWith({ username: 'username' });
     expect(verifyUserPassword).toBeCalledWith(null, 'password');
   });
 
   it('username password user is suspended', async () => {
-    findUserByIdentifierMock.mockResolvedValueOnce({ id: 'foo' });
-    verifyUserPasswordMock.mockResolvedValueOnce({ id: 'foo', isSuspended: true });
+    findUserByIdentifier.mockResolvedValueOnce({ id: 'foo' });
+    verifyUserPassword.mockResolvedValueOnce({ id: 'foo', isSuspended: true });
     const identifier = {
       username: 'username',
       password: 'password',
@@ -85,7 +77,7 @@ describe('identifier verification', () => {
       }),
     };
 
-    await expect(identifierPayloadVerification(ctx, new Provider(''))).rejects.toMatchError(
+    await expect(identifierPayloadVerification(ctx, createMockProvider())).rejects.toMatchError(
       new RequestError({ code: 'user.suspended', status: 401 })
     );
 
@@ -94,8 +86,8 @@ describe('identifier verification', () => {
   });
 
   it('email password', async () => {
-    findUserByIdentifierMock.mockResolvedValueOnce({ id: 'foo' });
-    verifyUserPasswordMock.mockResolvedValueOnce({ id: 'foo', isSuspended: false });
+    findUserByIdentifier.mockResolvedValueOnce({ id: 'foo' });
+    verifyUserPassword.mockResolvedValueOnce({ id: 'foo', isSuspended: false });
 
     const identifier = {
       email: 'email',
@@ -110,7 +102,7 @@ describe('identifier verification', () => {
       }),
     };
 
-    const result = await identifierPayloadVerification(ctx, new Provider(''));
+    const result = await identifierPayloadVerification(ctx, createMockProvider());
     expect(findUserByIdentifier).toBeCalledWith({ email: 'email' });
     expect(verifyUserPassword).toBeCalledWith({ id: 'foo' }, 'password');
     expect(result).toEqual({
@@ -120,8 +112,8 @@ describe('identifier verification', () => {
   });
 
   it('phone password', async () => {
-    findUserByIdentifierMock.mockResolvedValueOnce({ id: 'foo' });
-    verifyUserPasswordMock.mockResolvedValueOnce({ id: 'foo', isSuspended: false });
+    findUserByIdentifier.mockResolvedValueOnce({ id: 'foo' });
+    verifyUserPassword.mockResolvedValueOnce({ id: 'foo', isSuspended: false });
 
     const identifier = {
       phone: 'phone',
@@ -136,7 +128,7 @@ describe('identifier verification', () => {
       }),
     };
 
-    const result = await identifierPayloadVerification(ctx, new Provider(''));
+    const result = await identifierPayloadVerification(ctx, createMockProvider());
     expect(findUserByIdentifier).toBeCalledWith({ phone: 'phone' });
     expect(verifyUserPassword).toBeCalledWith({ id: 'foo' }, 'password');
     expect(result).toEqual({
@@ -156,8 +148,8 @@ describe('identifier verification', () => {
       }),
     };
 
-    const result = await identifierPayloadVerification(ctx, new Provider(''));
-    expect(verifyIdentifierByPasscodeMock).toBeCalledWith(
+    const result = await identifierPayloadVerification(ctx, createMockProvider());
+    expect(verifyIdentifierByPasscode).toBeCalledWith(
       { ...identifier, event: Event.SignIn },
       'jti',
       log
@@ -180,8 +172,8 @@ describe('identifier verification', () => {
       }),
     };
 
-    const result = await identifierPayloadVerification(ctx, new Provider(''));
-    expect(verifyIdentifierByPasscodeMock).toBeCalledWith(
+    const result = await identifierPayloadVerification(ctx, createMockProvider());
+    expect(verifyIdentifierByPasscode).toBeCalledWith(
       { ...identifier, event: Event.SignIn },
       'jti',
       log
@@ -204,10 +196,10 @@ describe('identifier verification', () => {
       }),
     };
 
-    const result = await identifierPayloadVerification(ctx, new Provider(''));
+    const result = await identifierPayloadVerification(ctx, createMockProvider());
 
     expect(verifySocialIdentity).toBeCalledWith(identifier, log);
-    expect(findUserByIdentifierMock).not.toBeCalled();
+    expect(findUserByIdentifier).not.toBeCalled();
 
     expect(result).toEqual({
       event: Event.SignIn,
@@ -242,7 +234,11 @@ describe('identifier verification', () => {
       }),
     };
 
-    const result = await identifierPayloadVerification(ctx, new Provider(''), interactionRecord);
+    const result = await identifierPayloadVerification(
+      ctx,
+      createMockProvider(),
+      interactionRecord
+    );
     expect(result).toEqual({
       event: Event.SignIn,
       identifiers: [
@@ -273,7 +269,7 @@ describe('identifier verification', () => {
       }),
     };
 
-    await expect(identifierPayloadVerification(ctx, new Provider(''))).rejects.toMatchError(
+    await expect(identifierPayloadVerification(ctx, createMockProvider())).rejects.toMatchError(
       new RequestError('session.connector_session_not_found')
     );
   });
@@ -303,7 +299,7 @@ describe('identifier verification', () => {
     };
 
     await expect(
-      identifierPayloadVerification(ctx, new Provider(''), interactionRecord)
+      identifierPayloadVerification(ctx, createMockProvider(), interactionRecord)
     ).rejects.toMatchError(new RequestError('session.connector_session_not_found'));
   });
 
@@ -319,12 +315,12 @@ describe('identifier verification', () => {
       }),
     };
 
-    const result = await identifierPayloadVerification(ctx, new Provider(''), {
+    const result = await identifierPayloadVerification(ctx, createMockProvider(), {
       event: Event.Register,
       identifiers: [oldIdentifier],
     });
 
-    expect(verifyIdentifierByPasscodeMock).toBeCalledWith(
+    expect(verifyIdentifierByPasscode).toBeCalledWith(
       { ...identifier, event: Event.SignIn },
       'jti',
       log
diff --git a/packages/core/src/routes/interaction/verifications/mandatory-user-profile-validation.test.ts b/packages/core/src/routes/interaction/verifications/mandatory-user-profile-validation.test.ts
index 9e0eca6f0..4318fb820 100644
--- a/packages/core/src/routes/interaction/verifications/mandatory-user-profile-validation.test.ts
+++ b/packages/core/src/routes/interaction/verifications/mandatory-user-profile-validation.test.ts
@@ -1,28 +1,26 @@
 import { Event, MissingProfile, SignInIdentifier } from '@logto/schemas';
+import { mockEsm, mockEsmWithActual, pickDefault } from '@logto/shared/esm';
 
 import { mockSignInExperience } from '#src/__mocks__/sign-in-experience.js';
 import RequestError from '#src/errors/RequestError/index.js';
-import { findUserById } from '#src/queries/user.js';
 import { createContextWithRouteParameters } from '#src/utils/test-utils.js';
 
 import type { IdentifierVerifiedInteractionResult } from '../types/index.js';
-import { isUserPasswordSet } from '../utils/index.js';
-import validateMandatoryUserProfile from './mandatory-user-profile-validation.js';
 
-jest.mock('oidc-provider', () => ({
-  Provider: jest.fn(() => ({
-    interactionDetails: jest.fn(async () => ({ params: {}, jti: 'jti' })),
-  })),
-}));
+const { jest } = import.meta;
 
-jest.mock('#src/queries/user.js', () => ({
+const { findUserById } = await mockEsmWithActual('#src/queries/user.js', () => ({
   findUserById: jest.fn(),
 }));
 
-jest.mock('../utils/index.js', () => ({
+const { isUserPasswordSet } = mockEsm('../utils/index.js', () => ({
   isUserPasswordSet: jest.fn(),
 }));
 
+const validateMandatoryUserProfile = await pickDefault(
+  import('./mandatory-user-profile-validation.js')
+);
+
 describe('validateMandatoryUserProfile', () => {
   const baseCtx = createContextWithRouteParameters();
   const interaction: IdentifierVerifiedInteractionResult = {
@@ -55,10 +53,10 @@ describe('validateMandatoryUserProfile', () => {
   });
 
   it('user account has username and password', async () => {
-    (findUserById as jest.Mock).mockResolvedValueOnce({
+    findUserById.mockResolvedValueOnce({
       username: 'foo',
     });
-    (isUserPasswordSet as jest.Mock).mockResolvedValueOnce(true);
+    isUserPasswordSet.mockResolvedValueOnce(true);
 
     const ctx = {
       ...baseCtx,
@@ -86,7 +84,7 @@ describe('validateMandatoryUserProfile', () => {
   });
 
   it('user account has email', async () => {
-    (findUserById as jest.Mock).mockResolvedValueOnce({
+    findUserById.mockResolvedValueOnce({
       primaryEmail: 'email',
     });
 
@@ -119,7 +117,7 @@ describe('validateMandatoryUserProfile', () => {
   });
 
   it('user account has phone', async () => {
-    (findUserById as jest.Mock).mockResolvedValueOnce({
+    findUserById.mockResolvedValueOnce({
       primaryPhone: 'phone',
     });
 
diff --git a/packages/core/src/routes/interaction/verifications/profile-verification-forgot-password.test.ts b/packages/core/src/routes/interaction/verifications/profile-verification-forgot-password.test.ts
index 0e1e2a1c5..8647d8aff 100644
--- a/packages/core/src/routes/interaction/verifications/profile-verification-forgot-password.test.ts
+++ b/packages/core/src/routes/interaction/verifications/profile-verification-forgot-password.test.ts
@@ -1,35 +1,30 @@
 import { Event } from '@logto/schemas';
-import { argon2Verify } from 'hash-wasm';
-import { Provider } from 'oidc-provider';
+import { mockEsm, mockEsmWithActual, pickDefault } from '@logto/shared/esm';
 
 import RequestError from '#src/errors/RequestError/index.js';
-import { findUserById } from '#src/queries/user.js';
+import { createMockProvider } from '#src/test-utils/oidc-provider.js';
 import { createContextWithRouteParameters } from '#src/utils/test-utils.js';
 
 import type { InteractionContext } from '../types/index.js';
-import { storeInteractionResult } from '../utils/interaction.js';
-import verifyProfile from './profile-verification.js';
 
-jest.mock('../utils/interaction.js', () => ({
+const { jest } = import.meta;
+
+const { storeInteractionResult } = mockEsm('../utils/interaction.js', () => ({
   storeInteractionResult: jest.fn(),
 }));
 
-jest.mock('oidc-provider', () => ({
-  Provider: jest.fn(() => ({
-    interactionDetails: jest.fn(async () => ({ params: {}, jti: 'jti' })),
-  })),
-}));
-
-jest.mock('#src/queries/user.js', () => ({
+const { findUserById } = await mockEsmWithActual('#src/queries/user.js', () => ({
   findUserById: jest.fn().mockResolvedValue({ id: 'foo', passwordEncrypted: 'passwordHash' }),
 }));
 
-jest.mock('hash-wasm', () => ({
+const { argon2Verify } = mockEsm('hash-wasm', () => ({
   argon2Verify: jest.fn(),
 }));
 
+const verifyProfile = await pickDefault(import('./profile-verification.js'));
+
 describe('forgot password interaction profile verification', () => {
-  const provider = new Provider('');
+  const provider = createMockProvider();
   const baseCtx = createContextWithRouteParameters();
 
   const interaction = {
@@ -55,7 +50,7 @@ describe('forgot password interaction profile verification', () => {
   });
 
   it('same password', async () => {
-    (argon2Verify as jest.Mock).mockResolvedValueOnce(true);
+    argon2Verify.mockResolvedValueOnce(true);
     const ctx: InteractionContext = {
       ...baseCtx,
       interactionPayload: {
diff --git a/packages/core/src/routes/interaction/verifications/profile-verification-profile-exist.test.ts b/packages/core/src/routes/interaction/verifications/profile-verification-profile-exist.test.ts
index 57bc9d52f..992b361cb 100644
--- a/packages/core/src/routes/interaction/verifications/profile-verification-profile-exist.test.ts
+++ b/packages/core/src/routes/interaction/verifications/profile-verification-profile-exist.test.ts
@@ -1,8 +1,8 @@
 import { Event } from '@logto/schemas';
-import { Provider } from 'oidc-provider';
+import { mockEsm, mockEsmWithActual, pickDefault } from '@logto/shared/esm';
 
 import RequestError from '#src/errors/RequestError/index.js';
-import { findUserById } from '#src/queries/user.js';
+import { createMockProvider } from '#src/test-utils/oidc-provider.js';
 import { createContextWithRouteParameters } from '#src/utils/test-utils.js';
 
 import type {
@@ -10,32 +10,28 @@ import type {
   IdentifierVerifiedInteractionResult,
   InteractionContext,
 } from '../types/index.js';
-import { storeInteractionResult } from '../utils/interaction.js';
-import verifyProfile from './profile-verification.js';
 
-jest.mock('oidc-provider', () => ({
-  Provider: jest.fn(() => ({
-    interactionDetails: jest.fn(async () => ({ params: {}, jti: 'jti' })),
-  })),
-}));
+const { jest } = import.meta;
 
-jest.mock('#src/queries/user.js', () => ({
+const { findUserById } = await mockEsmWithActual('#src/queries/user.js', () => ({
   findUserById: jest.fn().mockResolvedValue({ id: 'foo' }),
   hasUserWithEmail: jest.fn().mockResolvedValue(false),
   hasUserWithPhone: jest.fn().mockResolvedValue(false),
   hasUserWithIdentity: jest.fn().mockResolvedValue(false),
 }));
 
-jest.mock('../utils/interaction.js', () => ({
+const { storeInteractionResult } = mockEsm('../utils/interaction.js', () => ({
   storeInteractionResult: jest.fn(),
 }));
 
-jest.mock('../utils/index.js', () => ({
+mockEsm('../utils/index.js', () => ({
   isUserPasswordSet: jest.fn().mockResolvedValueOnce(true),
 }));
 
+const verifyProfile = await pickDefault(import('./profile-verification.js'));
+
 describe('Should throw when providing existing identifiers in profile', () => {
-  const provider = new Provider('');
+  const provider = createMockProvider();
   const baseCtx = createContextWithRouteParameters();
   const identifiers: Identifier[] = [
     { key: 'accountId', value: 'foo' },
@@ -54,7 +50,7 @@ describe('Should throw when providing existing identifiers in profile', () => {
   });
 
   it('username exists', async () => {
-    (findUserById as jest.Mock).mockResolvedValueOnce({ id: 'foo', username: 'foo' });
+    findUserById.mockResolvedValueOnce({ id: 'foo', username: 'foo' });
 
     const ctx: InteractionContext = {
       ...baseCtx,
@@ -75,7 +71,7 @@ describe('Should throw when providing existing identifiers in profile', () => {
   });
 
   it('email exists', async () => {
-    (findUserById as jest.Mock).mockResolvedValueOnce({ id: 'foo', primaryEmail: 'email' });
+    findUserById.mockResolvedValueOnce({ id: 'foo', primaryEmail: 'email' });
 
     const ctx: InteractionContext = {
       ...baseCtx,
@@ -96,7 +92,7 @@ describe('Should throw when providing existing identifiers in profile', () => {
   });
 
   it('phone exists', async () => {
-    (findUserById as jest.Mock).mockResolvedValueOnce({ id: 'foo', primaryPhone: 'phone' });
+    findUserById.mockResolvedValueOnce({ id: 'foo', primaryPhone: 'phone' });
 
     const ctx: InteractionContext = {
       ...baseCtx,
@@ -117,7 +113,7 @@ describe('Should throw when providing existing identifiers in profile', () => {
   });
 
   it('password exists', async () => {
-    (findUserById as jest.Mock).mockResolvedValueOnce({ id: 'foo' });
+    findUserById.mockResolvedValueOnce({ id: 'foo' });
 
     const ctx: InteractionContext = {
       ...baseCtx,
diff --git a/packages/core/src/routes/interaction/verifications/profile-verification-profile-registered.test.ts b/packages/core/src/routes/interaction/verifications/profile-verification-profile-registered.test.ts
index 0af867ba5..295af32bd 100644
--- a/packages/core/src/routes/interaction/verifications/profile-verification-profile-registered.test.ts
+++ b/packages/core/src/routes/interaction/verifications/profile-verification-profile-registered.test.ts
@@ -1,13 +1,8 @@
 import { Event } from '@logto/schemas';
-import { Provider } from 'oidc-provider';
+import { mockEsm, mockEsmWithActual, pickDefault } from '@logto/shared/esm';
 
 import RequestError from '#src/errors/RequestError/index.js';
-import {
-  hasUser,
-  hasUserWithEmail,
-  hasUserWithPhone,
-  hasUserWithIdentity,
-} from '#src/queries/user.js';
+import { createMockProvider } from '#src/test-utils/oidc-provider.js';
 import { createContextWithRouteParameters } from '#src/utils/test-utils.js';
 
 import type {
@@ -15,33 +10,30 @@ import type {
   InteractionContext,
   IdentifierVerifiedInteractionResult,
 } from '../types/index.js';
-import { storeInteractionResult } from '../utils/interaction.js';
-import verifyProfile from './profile-verification.js';
 
-jest.mock('oidc-provider', () => ({
-  Provider: jest.fn(() => ({
-    interactionDetails: jest.fn(async () => ({ params: {}, jti: 'jti' })),
-  })),
-}));
+const { jest } = import.meta;
 
-jest.mock('../utils/interaction.js', () => ({
+const { storeInteractionResult } = mockEsm('../utils/interaction.js', () => ({
   storeInteractionResult: jest.fn(),
 }));
 
-jest.mock('#src/queries/user.js', () => ({
-  hasUser: jest.fn().mockResolvedValue(false),
-  findUserById: jest.fn().mockResolvedValue({ id: 'foo' }),
-  hasUserWithEmail: jest.fn().mockResolvedValue(false),
-  hasUserWithPhone: jest.fn().mockResolvedValue(false),
-  hasUserWithIdentity: jest.fn().mockResolvedValue(false),
-}));
+const { hasUser, hasUserWithEmail, hasUserWithPhone, hasUserWithIdentity } =
+  await mockEsmWithActual('#src/queries/user.js', () => ({
+    hasUser: jest.fn().mockResolvedValue(false),
+    findUserById: jest.fn().mockResolvedValue({ id: 'foo' }),
+    hasUserWithEmail: jest.fn().mockResolvedValue(false),
+    hasUserWithPhone: jest.fn().mockResolvedValue(false),
+    hasUserWithIdentity: jest.fn().mockResolvedValue(false),
+  }));
 
-jest.mock('#src/connectors/index.js', () => ({
+mockEsm('#src/connectors/index.js', () => ({
   getLogtoConnectorById: jest.fn().mockResolvedValue({
     metadata: { target: 'logto' },
   }),
 }));
 
+const verifyProfile = await pickDefault(import('./profile-verification.js'));
+
 const baseCtx = createContextWithRouteParameters();
 const identifiers: Identifier[] = [
   { key: 'accountId', value: 'foo' },
@@ -49,7 +41,7 @@ const identifiers: Identifier[] = [
   { key: 'phoneVerified', value: '123456' },
   { key: 'social', connectorId: 'connectorId', userInfo: { id: 'foo' } },
 ];
-const provider = new Provider('');
+const provider = createMockProvider();
 
 const interaction: IdentifierVerifiedInteractionResult = {
   event: Event.Register,
@@ -140,7 +132,7 @@ describe('register payload guard', () => {
 
 describe('profile registered validation', () => {
   it('username is registered', async () => {
-    (hasUser as jest.Mock).mockResolvedValueOnce(true);
+    hasUser.mockResolvedValueOnce(true);
 
     const ctx: InteractionContext = {
       ...baseCtx,
@@ -163,7 +155,7 @@ describe('profile registered validation', () => {
   });
 
   it('email is registered', async () => {
-    (hasUserWithEmail as jest.Mock).mockResolvedValueOnce(true);
+    hasUserWithEmail.mockResolvedValueOnce(true);
 
     const ctx: InteractionContext = {
       ...baseCtx,
@@ -185,7 +177,7 @@ describe('profile registered validation', () => {
   });
 
   it('phone is registered', async () => {
-    (hasUserWithPhone as jest.Mock).mockResolvedValueOnce(true);
+    hasUserWithPhone.mockResolvedValueOnce(true);
 
     const ctx: InteractionContext = {
       ...baseCtx,
@@ -207,7 +199,7 @@ describe('profile registered validation', () => {
   });
 
   it('connector identity exist', async () => {
-    (hasUserWithIdentity as jest.Mock).mockResolvedValueOnce(true);
+    hasUserWithIdentity.mockResolvedValueOnce(true);
 
     const ctx: InteractionContext = {
       ...baseCtx,
diff --git a/packages/core/src/routes/interaction/verifications/profile-verification-protected-identifier.test.ts b/packages/core/src/routes/interaction/verifications/profile-verification-protected-identifier.test.ts
index 626cac78b..571a94aad 100644
--- a/packages/core/src/routes/interaction/verifications/profile-verification-protected-identifier.test.ts
+++ b/packages/core/src/routes/interaction/verifications/profile-verification-protected-identifier.test.ts
@@ -1,40 +1,37 @@
 import { Event } from '@logto/schemas';
-import { Provider } from 'oidc-provider';
+import { mockEsm, mockEsmWithActual, pickDefault } from '@logto/shared/esm';
 
 import RequestError from '#src/errors/RequestError/index.js';
+import { createMockProvider } from '#src/test-utils/oidc-provider.js';
 import { createContextWithRouteParameters } from '#src/utils/test-utils.js';
 
 import type { Identifier, InteractionContext } from '../types/index.js';
-import { storeInteractionResult } from '../utils/interaction.js';
-import verifyProfile from './profile-verification.js';
 
-jest.mock('oidc-provider', () => ({
-  Provider: jest.fn(() => ({
-    interactionDetails: jest.fn(async () => ({ params: {}, jti: 'jti' })),
-  })),
-}));
+const { jest } = import.meta;
 
-jest.mock('../utils/interaction.js', () => ({
+const { storeInteractionResult } = mockEsm('../utils/interaction.js', () => ({
   storeInteractionResult: jest.fn(),
 }));
 
-jest.mock('#src/queries/user.js', () => ({
+await mockEsmWithActual('#src/queries/user.js', () => ({
   findUserById: jest.fn().mockResolvedValue({ id: 'foo' }),
   hasUserWithEmail: jest.fn().mockResolvedValue(false),
   hasUserWithPhone: jest.fn().mockResolvedValue(false),
   hasUserWithIdentity: jest.fn().mockResolvedValue(false),
 }));
 
-jest.mock('#src/connectors/index.js', () => ({
+mockEsm('#src/connectors/index.js', () => ({
   getLogtoConnectorById: jest.fn().mockResolvedValue({
     metadata: { target: 'logto' },
   }),
 }));
 
+const verifyProfile = await pickDefault(import('./profile-verification.js'));
+
 describe('profile protected identifier verification', () => {
   const baseCtx = createContextWithRouteParameters();
   const interaction = { event: Event.SignIn, accountId: 'foo' };
-  const provider = new Provider('');
+  const provider = createMockProvider();
 
   afterEach(() => {
     jest.clearAllMocks();
diff --git a/packages/core/src/routes/interaction/verifications/user-identity-verification.test.ts b/packages/core/src/routes/interaction/verifications/user-identity-verification.test.ts
index 8ac0bc348..bc3c305a6 100644
--- a/packages/core/src/routes/interaction/verifications/user-identity-verification.test.ts
+++ b/packages/core/src/routes/interaction/verifications/user-identity-verification.test.ts
@@ -1,32 +1,28 @@
 import { Event } from '@logto/schemas';
-import { Provider } from 'oidc-provider';
+import { mockEsm, mockEsmDefault, mockEsmWithActual, pickDefault } from '@logto/shared/esm';
 
 import RequestError from '#src/errors/RequestError/index.js';
+import { createMockProvider } from '#src/test-utils/oidc-provider.js';
 import { createContextWithRouteParameters } from '#src/utils/test-utils.js';
 
 import type { InteractionContext, PayloadVerifiedInteractionResult } from '../types/index.js';
-import findUserByIdentifier from '../utils/find-user-by-identifier.js';
-import { storeInteractionResult } from '../utils/interaction.js';
-import userAccountVerification from './user-identity-verification.js';
 
-jest.mock('oidc-provider', () => ({
-  Provider: jest.fn(() => ({
-    interactionDetails: jest.fn(async () => ({ params: {}, jti: 'jti' })),
-  })),
-}));
+const { jest } = import.meta;
 
-jest.mock('../utils/find-user-by-identifier.js', () => jest.fn());
-jest.mock('#src/lib/social.js', () => ({
+const findUserByIdentifier = mockEsmDefault('../utils/find-user-by-identifier.js', () => jest.fn());
+
+mockEsm('#src/lib/social.js', () => ({
   findSocialRelatedUser: jest.fn().mockResolvedValue(null),
 }));
 
-jest.mock('../utils/interaction.js', () => ({
-  ...jest.requireActual('../utils/interaction.js'),
+const { storeInteractionResult } = await mockEsmWithActual('../utils/interaction.js', () => ({
   storeInteractionResult: jest.fn(),
 }));
 
+const userAccountVerification = await pickDefault(import('./user-identity-verification.js'));
+
 describe('userAccountVerification', () => {
-  const findUserByIdentifierMock = findUserByIdentifier as jest.Mock;
+  const findUserByIdentifierMock = findUserByIdentifier;
 
   const ctx: InteractionContext = {
     ...createContextWithRouteParameters(),
@@ -34,7 +30,7 @@ describe('userAccountVerification', () => {
       event: Event.SignIn,
     },
   };
-  const provider = new Provider('');
+  const provider = createMockProvider();
 
   afterEach(() => {
     jest.clearAllMocks();
diff --git a/packages/core/src/routes/log.test.ts b/packages/core/src/routes/log.test.ts
index 4914c0b84..ae3d50504 100644
--- a/packages/core/src/routes/log.test.ts
+++ b/packages/core/src/routes/log.test.ts
@@ -1,25 +1,21 @@
-import type { LogCondition } from '#src/queries/log.js';
-import logRoutes from '#src/routes/log.js';
+import { mockEsm, pickDefault } from '@logto/shared/esm';
+
 import { createRequester } from '#src/utils/test-utils.js';
 
+const { jest } = import.meta;
+
 const mockBody = { type: 'a', payload: {}, createdAt: 123 };
 const mockLog = { id: '1', ...mockBody };
 const mockLogs = [mockLog, { id: '2', ...mockBody }];
 
-const countLogs = jest.fn(async (condition: LogCondition) => ({
-  count: mockLogs.length,
-}));
-const findLogs = jest.fn(
-  async (limit: number, offset: number, condition: LogCondition) => mockLogs
-);
-const findLogById = jest.fn(async (id: string) => mockLog);
-
-jest.mock('#src/queries/log.js', () => ({
-  countLogs: async (condition: LogCondition) => countLogs(condition),
-  findLogs: async (limit: number, offset: number, condition: LogCondition) =>
-    findLogs(limit, offset, condition),
-  findLogById: async (id: string) => findLogById(id),
+const { countLogs, findLogs, findLogById } = mockEsm('#src/queries/log.js', () => ({
+  countLogs: jest.fn().mockResolvedValue({
+    count: mockLogs.length,
+  }),
+  findLogs: jest.fn().mockResolvedValue(mockLogs),
+  findLogById: jest.fn().mockResolvedValue(mockLog),
 }));
+const logRoutes = await pickDefault(import('./log.js'));
 
 describe('logRoutes', () => {
   const logRequest = createRequester({ authedRoutes: logRoutes });
diff --git a/packages/core/src/routes/phrase.content-language.test.ts b/packages/core/src/routes/phrase.content-language.test.ts
index 39e01b79c..642596862 100644
--- a/packages/core/src/routes/phrase.content-language.test.ts
+++ b/packages/core/src/routes/phrase.content-language.test.ts
@@ -1,51 +1,42 @@
 import en from '@logto/phrases-ui/lib/locales/en.js';
-import { Provider } from 'oidc-provider';
+import { mockEsmWithActual, pickDefault } from '@logto/shared/esm';
 
 import { trTrTag, zhCnTag, zhHkTag } from '#src/__mocks__/custom-phrase.js';
 import { mockSignInExperience } from '#src/__mocks__/index.js';
-import phraseRoutes from '#src/routes/phrase.js';
+import { createMockProvider } from '#src/test-utils/oidc-provider.js';
 import { createRequester } from '#src/utils/test-utils.js';
 
-const mockApplicationId = 'mockApplicationIdValue';
-
-const interactionDetails: jest.MockedFunction<() => Promise<unknown>> = jest.fn(async () => ({
-  params: { client_id: mockApplicationId },
-}));
-
-jest.mock('oidc-provider', () => ({
-  Provider: jest.fn(() => ({
-    interactionDetails,
-  })),
-}));
+const { jest } = import.meta;
 
 const fallbackLanguage = trTrTag;
 const unsupportedLanguageX = 'xx-XX';
 const unsupportedLanguageY = 'yy-YY';
 
-const findDefaultSignInExperience = jest.fn(async () => ({
-  ...mockSignInExperience,
-  languageInfo: {
-    autoDetect: true,
-    fallbackLanguage,
-  },
-}));
+const { findDefaultSignInExperience } = await mockEsmWithActual(
+  '#src/queries/sign-in-experience.js',
+  () => ({
+    findDefaultSignInExperience: jest.fn(async () => ({
+      ...mockSignInExperience,
+      languageInfo: {
+        autoDetect: true,
+        fallbackLanguage,
+      },
+    })),
+  })
+);
 
-jest.mock('#src/queries/sign-in-experience.js', () => ({
-  findDefaultSignInExperience: async () => findDefaultSignInExperience(),
-}));
-
-jest.mock('#src/queries/custom-phrase.js', () => ({
+await mockEsmWithActual('#src/queries/custom-phrase.js', () => ({
   findAllCustomLanguageTags: async () => [trTrTag, zhCnTag],
 }));
 
-jest.mock('#src/lib/phrase.js', () => ({
-  ...jest.requireActual('#src/lib/phrase.js'),
+await mockEsmWithActual('#src/lib/phrase.js', () => ({
   getPhrase: jest.fn().mockResolvedValue(en),
 }));
+const phraseRoutes = await pickDefault(import('./phrase.js'));
 
 const phraseRequest = createRequester({
   anonymousRoutes: phraseRoutes,
-  provider: new Provider(''),
+  provider: createMockProvider(),
 });
 
 afterEach(() => {
diff --git a/packages/core/src/routes/phrase.test.ts b/packages/core/src/routes/phrase.test.ts
index 03a1a21aa..413d28755 100644
--- a/packages/core/src/routes/phrase.test.ts
+++ b/packages/core/src/routes/phrase.test.ts
@@ -4,67 +4,48 @@ import {
   adminConsoleApplicationId,
   adminConsoleSignInExperience,
 } from '@logto/schemas/lib/seeds/index.js';
-import { Provider } from 'oidc-provider';
+import { mockEsm, mockEsmWithActual, pickDefault } from '@logto/shared/esm';
 
 import { zhCnTag } from '#src/__mocks__/custom-phrase.js';
 import { mockSignInExperience } from '#src/__mocks__/index.js';
-import * as detectLanguage from '#src/i18n/detect-language.js';
-import phraseRoutes from '#src/routes/phrase.js';
-import { createRequester } from '#src/utils/test-utils.js';
+import { createMockProvider } from '#src/test-utils/oidc-provider.js';
 
-const mockApplicationId = 'mockApplicationIdValue';
-
-const interactionDetails: jest.MockedFunction<() => Promise<unknown>> = jest.fn(async () => ({
-  params: { client_id: mockApplicationId },
-}));
-
-jest.mock('oidc-provider', () => ({
-  Provider: jest.fn(() => ({
-    interactionDetails,
-  })),
-}));
+const { jest } = import.meta;
 
 const customizedLanguage = zhCnTag;
 
-const findDefaultSignInExperience = jest.fn(
-  async (): Promise<SignInExperience> => ({
-    ...mockSignInExperience,
-    languageInfo: {
-      autoDetect: true,
-      fallbackLanguage: customizedLanguage,
-    },
-  })
-);
-
-jest.mock('#src/queries/sign-in-experience.js', () => ({
-  findDefaultSignInExperience: async () => findDefaultSignInExperience(),
+const { findDefaultSignInExperience } = mockEsm('#src/queries/sign-in-experience.js', () => ({
+  findDefaultSignInExperience: jest.fn(
+    async (): Promise<SignInExperience> => ({
+      ...mockSignInExperience,
+      languageInfo: {
+        autoDetect: true,
+        fallbackLanguage: customizedLanguage,
+      },
+    })
+  ),
 }));
 
-const detectLanguageSpy = jest.spyOn(detectLanguage, 'default');
-
-const findAllCustomLanguageTags = jest.fn(async () => [customizedLanguage]);
-const findCustomPhraseByLanguageTag = jest.fn(async (tag: string) => ({}));
-
-jest.mock('#src/queries/custom-phrase.js', () => ({
-  findAllCustomLanguageTags: async () => findAllCustomLanguageTags(),
-  findCustomPhraseByLanguageTag: async (tag: string) => findCustomPhraseByLanguageTag(tag),
+const { default: detectLanguageSpy } = mockEsm('#src/i18n/detect-language.js', () => ({
+  default: jest.fn().mockReturnValue([]),
 }));
 
-const getPhrase = jest.fn(async (language: string, customLanguages: string[]) => zhCN);
-
-jest.mock('#src/lib/phrase.js', () => ({
-  ...jest.requireActual('#src/lib/phrase.js'),
-  getPhrase: async (language: string, customLanguages: string[]) =>
-    getPhrase(language, customLanguages),
+const { findAllCustomLanguageTags } = mockEsm('#src/queries/custom-phrase.js', () => ({
+  findAllCustomLanguageTags: jest.fn(async () => [customizedLanguage]),
+  findCustomPhraseByLanguageTag: jest.fn(async (tag: string) => ({})),
 }));
 
+const { getPhrase } = await mockEsmWithActual('#src/lib/phrase.js', () => ({
+  getPhrase: jest.fn(async () => zhCN),
+}));
+
+const interactionDetails = jest.fn();
+const phraseRoutes = await pickDefault(import('./phrase.js'));
+
+const { createRequester } = await import('#src/utils/test-utils.js');
 const phraseRequest = createRequester({
   anonymousRoutes: phraseRoutes,
-  provider: new Provider(''),
-});
-
-afterEach(() => {
-  jest.clearAllMocks();
+  provider: createMockProvider(interactionDetails),
 });
 
 describe('when the application is admin-console', () => {
@@ -74,6 +55,10 @@ describe('when the application is admin-console', () => {
     });
   });
 
+  afterEach(() => {
+    jest.clearAllMocks();
+  });
+
   it('should call interactionDetails', async () => {
     await expect(phraseRequest.get('/phrase')).resolves.toHaveProperty('status', 200);
     expect(interactionDetails).toBeCalledTimes(1);
@@ -104,6 +89,18 @@ describe('when the application is admin-console', () => {
 });
 
 describe('when the application is not admin-console', () => {
+  beforeEach(() => {
+    interactionDetails.mockResolvedValue({
+      params: {},
+      jti: 'jti',
+      client_id: 'mockApplicationId',
+    });
+  });
+
+  afterEach(() => {
+    jest.clearAllMocks();
+  });
+
   it('should call interactionDetails', async () => {
     await expect(phraseRequest.get('/phrase')).resolves.toHaveProperty('status', 200);
     expect(interactionDetails).toBeCalledTimes(1);
diff --git a/packages/core/src/routes/profile.test.ts b/packages/core/src/routes/profile.test.ts
index bc6023022..5d2dcd2ac 100644
--- a/packages/core/src/routes/profile.test.ts
+++ b/packages/core/src/routes/profile.test.ts
@@ -1,8 +1,7 @@
-/* eslint-disable max-lines */
 import type { CreateUser, User } from '@logto/schemas';
 import { ConnectorType } from '@logto/schemas';
+import { mockEsm, mockEsmWithActual } from '@logto/shared/esm';
 import { getUnixTime } from 'date-fns';
-import { Provider } from 'oidc-provider';
 
 import {
   mockLogtoConnectorList,
@@ -11,101 +10,79 @@ import {
   mockUserResponse,
 } from '#src/__mocks__/index.js';
 import type { SocialUserInfo } from '#src/connectors/types.js';
+import { createMockProvider } from '#src/test-utils/oidc-provider.js';
 import { createRequester } from '#src/utils/test-utils.js';
 
-import profileRoutes, { profileRoute } from './profile.js';
+const { jest } = import.meta;
 
-const mockFindUserById = jest.fn(async (): Promise<User> => mockUser);
-const mockHasUser = jest.fn(async () => false);
-const mockHasUserWithEmail = jest.fn(async () => false);
-const mockHasUserWithPhone = jest.fn(async () => false);
-const mockUpdateUserById = jest.fn(
-  async (_, data: Partial<CreateUser>): Promise<User> => ({
-    ...mockUser,
-    ...data,
-  })
-);
-const mockDeleteUserIdentity = jest.fn();
-const encryptUserPassword = jest.fn(async (password: string) => ({
-  passwordEncrypted: password + '_user1',
-  passwordEncryptionMethod: 'Argon2i',
-}));
-const mockArgon2Verify = jest.fn(async (password: string) => password === mockPasswordEncrypted);
-const mockGetSession = jest.fn();
-
-jest.mock('oidc-provider', () => ({
-  Provider: jest.fn(() => ({
-    Session: {
-      get: async () => mockGetSession(),
-    },
-  })),
-}));
-
-jest.mock('#src/lib/user.js', () => ({
-  ...jest.requireActual('#src/lib/user.js'),
-  encryptUserPassword: async (password: string) => encryptUserPassword(password),
-}));
-
-const mockGetLogtoConnectorById = jest.fn(async () => ({
+const getLogtoConnectorById = jest.fn(async () => ({
   dbEntry: { enabled: true },
   metadata: { id: 'connectorId', target: 'mock_social' },
   type: ConnectorType.Social,
   getAuthorizationUri: jest.fn(async () => ''),
 }));
 
-jest.mock('#src/connectors/index.js', () => ({
-  getLogtoConnectors: jest.fn(async () => mockLogtoConnectorList),
-  getLogtoConnectorById: jest.fn(async () => mockGetLogtoConnectorById()),
+mockEsm('#src/connectors/index.js', () => ({
+  getLogtoConnectors: mockLogtoConnectorList,
+  getLogtoConnectorById,
 }));
 
-const mockFindSocialRelatedUser = jest.fn(async () => [
-  { id: 'user1', identities: {}, isSuspended: false },
-]);
-const mockGetUserInfoByAuthCode = jest.fn();
-jest.mock('#src/lib/social.js', () => ({
-  ...jest.requireActual('#src/lib/social.js'),
-  findSocialRelatedUser: async () => mockFindSocialRelatedUser(),
-  getUserInfoByAuthCode: async () => mockGetUserInfoByAuthCode(),
+const { getUserInfoByAuthCode } = await mockEsmWithActual('#src/lib/social.js', () => ({
+  findSocialRelatedUser: jest.fn(async () => [{ id: 'user1', identities: {}, isSuspended: false }]),
+  getUserInfoByAuthCode: jest.fn(),
 }));
 
-jest.mock('#src/queries/user.js', () => ({
-  ...jest.requireActual('#src/queries/user.js'),
-  findUserById: async () => mockFindUserById(),
-  hasUser: async () => mockHasUser(),
-  hasUserWithEmail: async () => mockHasUserWithEmail(),
-  hasUserWithPhone: async () => mockHasUserWithPhone(),
-  updateUserById: async (id: string, data: Partial<CreateUser>) => mockUpdateUserById(id, data),
-  deleteUserIdentity: async (...args: unknown[]) => mockDeleteUserIdentity(...args),
+const {
+  findUserById,
+  hasUser,
+  hasUserWithEmail,
+  hasUserWithPhone,
+  updateUserById,
+  deleteUserIdentity,
+} = await mockEsmWithActual('#src/queries/user.js', () => ({
+  findUserById: jest.fn(async (): Promise<User> => mockUser),
+  hasUser: jest.fn(async () => false),
+  hasUserWithEmail: jest.fn(async () => false),
+  hasUserWithPhone: jest.fn(async () => false),
+  updateUserById: jest.fn(
+    async (_, data: Partial<CreateUser>): Promise<User> => ({
+      ...mockUser,
+      ...data,
+    })
+  ),
+  deleteUserIdentity: jest.fn(),
 }));
 
-const mockFindDefaultSignInExperience = jest.fn(async () => ({
-  signUp: {
-    identifier: [],
-    password: false,
-    verify: false,
-  },
+const { encryptUserPassword } = await mockEsmWithActual('#src/lib/user.js', () => ({
+  encryptUserPassword: jest.fn(async (password: string) => ({
+    passwordEncrypted: password + '_user1',
+    passwordEncryptionMethod: 'Argon2i',
+  })),
 }));
 
-jest.mock('#src/queries/sign-in-experience.js', () => ({
-  findDefaultSignInExperience: jest.fn(async () => mockFindDefaultSignInExperience()),
+mockEsm('#src/queries/sign-in-experience.js', () => ({
+  findDefaultSignInExperience: async () => ({
+    signUp: {
+      identifier: [],
+      password: false,
+      verify: false,
+    },
+  }),
 }));
 
-jest.mock('hash-wasm', () => ({
-  argon2Verify: async (password: string) => mockArgon2Verify(password),
+const { argon2Verify } = mockEsm('hash-wasm', () => ({
+  argon2Verify: jest.fn(async (password: string) => password === mockPasswordEncrypted),
 }));
 
+const { default: profileRoutes, profileRoute } = await import('./profile.js');
+
 describe('session -> profileRoutes', () => {
-  beforeEach(() => {
-    jest.clearAllMocks();
-    mockGetSession.mockImplementation(async () => ({
-      accountId: 'id',
-      loginTs: getUnixTime(new Date()) - 60,
-    }));
-  });
-
+  const provider = createMockProvider();
+  // @ts-expect-error for testing
+  const mockGetSession: jest.Mock = jest.spyOn(provider.Session, 'get');
   const sessionRequest = createRequester({
     anonymousRoutes: profileRoutes,
-    provider: new Provider(''),
+    provider,
     middlewares: [
       async (ctx, next) => {
         ctx.addLogContext = jest.fn();
@@ -116,6 +93,14 @@ describe('session -> profileRoutes', () => {
     ],
   });
 
+  beforeEach(() => {
+    jest.clearAllMocks();
+    mockGetSession.mockImplementation(async () => ({
+      accountId: 'id',
+      loginTs: getUnixTime(new Date()) - 60,
+    }));
+  });
+
   describe('GET /session/profile', () => {
     it('should return current user data', async () => {
       const response = await sessionRequest.get(profileRoute);
@@ -146,7 +131,7 @@ describe('session -> profileRoutes', () => {
 
       const response = await sessionRequest.patch(profileRoute).send(updatedUserInfo);
 
-      expect(mockUpdateUserById).toBeCalledWith('id', expect.objectContaining(updatedUserInfo));
+      expect(updateUserById).toBeCalledWith('id', expect.objectContaining(updatedUserInfo));
       expect(response.statusCode).toEqual(204);
     });
 
@@ -175,7 +160,7 @@ describe('session -> profileRoutes', () => {
         .send({ username: 'test' });
 
       expect(response.statusCode).toEqual(422);
-      expect(mockUpdateUserById).not.toBeCalled();
+      expect(updateUserById).not.toBeCalled();
     });
 
     it('should update username with the new value', async () => {
@@ -190,7 +175,7 @@ describe('session -> profileRoutes', () => {
     });
 
     it('should throw when username is already in use', async () => {
-      mockHasUser.mockImplementationOnce(async () => true);
+      hasUser.mockImplementationOnce(async () => true);
 
       const response = await sessionRequest
         .patch(`${profileRoute}/username`)
@@ -212,7 +197,7 @@ describe('session -> profileRoutes', () => {
         .send({ password: mockPasswordEncrypted });
 
       expect(response.statusCode).toEqual(422);
-      expect(mockUpdateUserById).not.toBeCalled();
+      expect(updateUserById).not.toBeCalled();
     });
 
     it('should update password with the new value', async () => {
@@ -220,7 +205,7 @@ describe('session -> profileRoutes', () => {
         .patch(`${profileRoute}/password`)
         .send({ password: mockPasswordEncrypted });
 
-      expect(mockUpdateUserById).toBeCalledWith(
+      expect(updateUserById).toBeCalledWith(
         'id',
         expect.objectContaining({
           passwordEncrypted: 'a1b2c3_user1',
@@ -235,14 +220,14 @@ describe('session -> profileRoutes', () => {
         passwordEncrypted: password,
         passwordEncryptionMethod: 'Argon2i',
       }));
-      mockArgon2Verify.mockResolvedValueOnce(true);
+      argon2Verify.mockResolvedValueOnce(true);
 
       const response = await sessionRequest
         .patch(`${profileRoute}/password`)
         .send({ password: 'password' });
 
       expect(response.statusCode).toEqual(422);
-      expect(mockUpdateUserById).not.toBeCalled();
+      expect(updateUserById).not.toBeCalled();
     });
   });
 
@@ -258,7 +243,7 @@ describe('session -> profileRoutes', () => {
         .send({ primaryEmail: 'test@logto.io' });
 
       expect(response.statusCode).toEqual(422);
-      expect(mockUpdateUserById).not.toBeCalled();
+      expect(updateUserById).not.toBeCalled();
     });
 
     it('should link email address to the user profile', async () => {
@@ -267,7 +252,7 @@ describe('session -> profileRoutes', () => {
         .patch(`${profileRoute}/email`)
         .send({ primaryEmail: mockEmailAddress });
 
-      expect(mockUpdateUserById).toBeCalledWith(
+      expect(updateUserById).toBeCalledWith(
         'id',
         expect.objectContaining({
           primaryEmail: mockEmailAddress,
@@ -277,25 +262,25 @@ describe('session -> profileRoutes', () => {
     });
 
     it('should throw when email address already exists', async () => {
-      mockHasUserWithEmail.mockImplementationOnce(async () => true);
+      hasUserWithEmail.mockImplementationOnce(async () => true);
 
       const response = await sessionRequest
         .patch(`${profileRoute}/email`)
         .send({ primaryEmail: mockUser.primaryEmail });
 
       expect(response.statusCode).toEqual(422);
-      expect(mockUpdateUserById).not.toBeCalled();
+      expect(updateUserById).not.toBeCalled();
     });
 
     it('should throw when email address is invalid', async () => {
-      mockHasUserWithEmail.mockImplementationOnce(async () => true);
+      hasUserWithEmail.mockImplementationOnce(async () => true);
 
       const response = await sessionRequest
         .patch(`${profileRoute}/email`)
         .send({ primaryEmail: 'test' });
 
       expect(response.statusCode).toEqual(400);
-      expect(mockUpdateUserById).not.toBeCalled();
+      expect(updateUserById).not.toBeCalled();
     });
 
     it('should throw if last authentication time is over 10 mins ago on unlinking email', async () => {
@@ -307,13 +292,13 @@ describe('session -> profileRoutes', () => {
       const response = await sessionRequest.delete(`${profileRoute}/email`);
 
       expect(response.statusCode).toEqual(422);
-      expect(mockUpdateUserById).not.toBeCalled();
+      expect(updateUserById).not.toBeCalled();
     });
 
     it('should unlink email address from user', async () => {
       const response = await sessionRequest.delete(`${profileRoute}/email`);
       expect(response.statusCode).toEqual(204);
-      expect(mockUpdateUserById).toBeCalledWith(
+      expect(updateUserById).toBeCalledWith(
         'id',
         expect.objectContaining({
           primaryEmail: null,
@@ -322,11 +307,11 @@ describe('session -> profileRoutes', () => {
     });
 
     it('should throw when no email address found in user on unlinking email', async () => {
-      mockFindUserById.mockImplementationOnce(async () => ({ ...mockUser, primaryEmail: null }));
+      findUserById.mockImplementationOnce(async () => ({ ...mockUser, primaryEmail: null }));
       const response = await sessionRequest.delete(`${profileRoute}/email`);
 
       expect(response.statusCode).toEqual(422);
-      expect(mockUpdateUserById).not.toBeCalled();
+      expect(updateUserById).not.toBeCalled();
     });
   });
 
@@ -342,7 +327,7 @@ describe('session -> profileRoutes', () => {
         .send({ primaryPhone: '6533333333' });
 
       expect(updateResponse.statusCode).toEqual(422);
-      expect(mockUpdateUserById).not.toBeCalled();
+      expect(updateUserById).not.toBeCalled();
     });
 
     it('should link phone number to the user profile', async () => {
@@ -351,7 +336,7 @@ describe('session -> profileRoutes', () => {
         .patch(`${profileRoute}/phone`)
         .send({ primaryPhone: mockPhoneNumber });
 
-      expect(mockUpdateUserById).toBeCalledWith(
+      expect(updateUserById).toBeCalledWith(
         'id',
         expect.objectContaining({
           primaryPhone: mockPhoneNumber,
@@ -361,25 +346,25 @@ describe('session -> profileRoutes', () => {
     });
 
     it('should throw when phone number already exists on linking phone number', async () => {
-      mockHasUserWithPhone.mockImplementationOnce(async () => true);
+      hasUserWithPhone.mockImplementationOnce(async () => true);
 
       const response = await sessionRequest
         .patch(`${profileRoute}/phone`)
         .send({ primaryPhone: mockUser.primaryPhone });
 
       expect(response.statusCode).toEqual(422);
-      expect(mockUpdateUserById).not.toBeCalled();
+      expect(updateUserById).not.toBeCalled();
     });
 
     it('should throw when phone number is invalid', async () => {
-      mockHasUserWithPhone.mockImplementationOnce(async () => true);
+      hasUserWithPhone.mockImplementationOnce(async () => true);
 
       const response = await sessionRequest
         .patch(`${profileRoute}/phone`)
         .send({ primaryPhone: 'invalid' });
 
       expect(response.statusCode).toEqual(400);
-      expect(mockUpdateUserById).not.toBeCalled();
+      expect(updateUserById).not.toBeCalled();
     });
 
     it('should throw if last authentication time is over 10 mins ago on unlinking phone number', async () => {
@@ -391,13 +376,13 @@ describe('session -> profileRoutes', () => {
       const response = await sessionRequest.delete(`${profileRoute}/phone`);
 
       expect(response.statusCode).toEqual(422);
-      expect(mockUpdateUserById).not.toBeCalled();
+      expect(updateUserById).not.toBeCalled();
     });
 
     it('should unlink phone number from user', async () => {
       const response = await sessionRequest.delete(`${profileRoute}/phone`);
       expect(response.statusCode).toEqual(204);
-      expect(mockUpdateUserById).toBeCalledWith(
+      expect(updateUserById).toBeCalledWith(
         'id',
         expect.objectContaining({
           primaryPhone: null,
@@ -406,11 +391,11 @@ describe('session -> profileRoutes', () => {
     });
 
     it('should throw when no phone number found in user on unlinking phone number', async () => {
-      mockFindUserById.mockImplementationOnce(async () => ({ ...mockUser, primaryPhone: null }));
+      findUserById.mockImplementationOnce(async () => ({ ...mockUser, primaryPhone: null }));
       const response = await sessionRequest.delete(`${profileRoute}/phone`);
 
       expect(response.statusCode).toEqual(422);
-      expect(mockUpdateUserById).not.toBeCalled();
+      expect(updateUserById).not.toBeCalled();
     });
   });
 
@@ -423,7 +408,7 @@ describe('session -> profileRoutes', () => {
         email: 'johndoe@social.com',
         phone: '123456789',
       };
-      mockGetUserInfoByAuthCode.mockReturnValueOnce(mockSocialUserInfo);
+      getUserInfoByAuthCode.mockReturnValueOnce(mockSocialUserInfo);
 
       const response = await sessionRequest.patch(`${profileRoute}/identities`).send({
         connectorId: 'connectorId',
@@ -431,7 +416,7 @@ describe('session -> profileRoutes', () => {
       });
 
       expect(response.statusCode).toEqual(204);
-      expect(mockUpdateUserById).toBeCalledWith(
+      expect(updateUserById).toBeCalledWith(
         'id',
         expect.objectContaining({
           identities: {
@@ -456,7 +441,7 @@ describe('session -> profileRoutes', () => {
       });
 
       expect(response.statusCode).toEqual(401);
-      expect(mockUpdateUserById).not.toBeCalled();
+      expect(updateUserById).not.toBeCalled();
     });
 
     it('should throw if last authentication time is over 10 mins ago on linking email', async () => {
@@ -470,11 +455,11 @@ describe('session -> profileRoutes', () => {
         .send({ connectorId: 'connectorId', data: { code: '123456' } });
 
       expect(response.statusCode).toEqual(422);
-      expect(mockUpdateUserById).not.toBeCalled();
+      expect(updateUserById).not.toBeCalled();
     });
 
     it('should unlink social identities from user', async () => {
-      mockFindUserById.mockImplementationOnce(async () => ({
+      findUserById.mockImplementationOnce(async () => ({
         ...mockUser,
         identities: {
           mock_social: {
@@ -490,8 +475,7 @@ describe('session -> profileRoutes', () => {
       const response = await sessionRequest.delete(`${profileRoute}/identities/mock_social`);
 
       expect(response.statusCode).toEqual(204);
-      expect(mockDeleteUserIdentity).toBeCalledWith('id', 'mock_social');
+      expect(deleteUserIdentity).toBeCalledWith('id', 'mock_social');
     });
   });
 });
-/* eslint-enable max-lines */
diff --git a/packages/core/src/routes/profile.ts b/packages/core/src/routes/profile.ts
index b815c1f5a..991322411 100644
--- a/packages/core/src/routes/profile.ts
+++ b/packages/core/src/routes/profile.ts
@@ -65,15 +65,12 @@ export default function profileRoutes<T extends AnonymousRouter>(router: T, prov
     }),
     async (ctx, next) => {
       const userId = await checkSessionHealth(ctx, provider, verificationTimeout);
-
       assertThat(userId, new RequestError({ code: 'auth.unauthorized', status: 401 }));
 
       const { username } = ctx.guard.body;
-
       await checkIdentifierCollision({ username }, userId);
 
       const user = await updateUserById(userId, { username }, 'replace');
-
       ctx.body = pick(user, ...userInfoSelectFields);
 
       return next();
diff --git a/packages/core/src/routes/resource.test.ts b/packages/core/src/routes/resource.test.ts
index b65f32740..03da2cd92 100644
--- a/packages/core/src/routes/resource.test.ts
+++ b/packages/core/src/routes/resource.test.ts
@@ -1,34 +1,33 @@
 import type { Resource, CreateResource } from '@logto/schemas';
+import { mockEsm, pickDefault } from '@logto/shared/esm';
 
 import { mockResource } from '#src/__mocks__/index.js';
 import { createRequester } from '#src/utils/test-utils.js';
 
-import resourceRoutes from './resource.js';
+const { jest } = import.meta;
 
-jest.mock('#src/queries/resource.js', () => ({
-  findTotalNumberOfResources: jest.fn(async () => ({ count: 10 })),
-  findAllResources: jest.fn(async (): Promise<Resource[]> => [mockResource]),
-  findResourceById: jest.fn(async (): Promise<Resource> => mockResource),
-  insertResource: jest.fn(
-    async (body: CreateResource): Promise<Resource> => ({
-      ...mockResource,
-      ...body,
-    })
-  ),
-  updateResourceById: jest.fn(
-    async (_, data: Partial<CreateResource>): Promise<Resource> => ({
-      ...mockResource,
-      ...data,
-    })
-  ),
+mockEsm('#src/queries/resource.js', () => ({
+  findTotalNumberOfResources: async () => ({ count: 10 }),
+  findAllResources: async (): Promise<Resource[]> => [mockResource],
+  findResourceById: async (): Promise<Resource> => mockResource,
+  insertResource: async (body: CreateResource): Promise<Resource> => ({
+    ...mockResource,
+    ...body,
+  }),
+  updateResourceById: async (_: unknown, data: Partial<CreateResource>): Promise<Resource> => ({
+    ...mockResource,
+    ...data,
+  }),
   deleteResourceById: jest.fn(),
 }));
 
-jest.mock('@logto/shared', () => ({
+mockEsm('@logto/shared', () => ({
   // eslint-disable-next-line unicorn/consistent-function-scoping
-  buildIdGenerator: jest.fn(() => () => 'randomId'),
+  buildIdGenerator: () => () => 'randomId',
 }));
 
+const resourceRoutes = await pickDefault(import('./resource.js'));
+
 describe('resource routes', () => {
   const resourceRequest = createRequester({ authedRoutes: resourceRoutes });
 
diff --git a/packages/core/src/routes/role.test.ts b/packages/core/src/routes/role.test.ts
index 5d72ff2e8..a31447a40 100644
--- a/packages/core/src/routes/role.test.ts
+++ b/packages/core/src/routes/role.test.ts
@@ -1,13 +1,15 @@
 import type { Role } from '@logto/schemas';
+import { mockEsm, pickDefault } from '@logto/shared/esm';
 
 import { mockRole } from '#src/__mocks__/index.js';
 import { createRequester } from '#src/utils/test-utils.js';
 
-import roleRoutes from './role.js';
+const { jest } = import.meta;
 
-jest.mock('#src/queries/roles.js', () => ({
+mockEsm('#src/queries/roles.js', () => ({
   findAllRoles: jest.fn(async (): Promise<Role[]> => [mockRole]),
 }));
+const roleRoutes = await pickDefault(import('./role.js'));
 
 describe('role routes', () => {
   const roleRequester = createRequester({ authedRoutes: roleRoutes });
diff --git a/packages/core/src/routes/session/utils.test.ts b/packages/core/src/routes/session/utils.test.ts
index fcf3488fd..b9ea84d72 100644
--- a/packages/core/src/routes/session/utils.test.ts
+++ b/packages/core/src/routes/session/utils.test.ts
@@ -1,11 +1,11 @@
 import type { User } from '@logto/schemas';
 import { UserRole, SignInIdentifier } from '@logto/schemas';
-import { createMockContext } from '@shopify/jest-koa-mocks';
 import type { Nullable } from '@silverhand/essentials';
 import { Provider } from 'oidc-provider';
 
 import { mockSignInExperience, mockSignInMethod, mockUser } from '#src/__mocks__/index.js';
 import RequestError from '#src/errors/RequestError/index.js';
+import createMockContext from '#src/test-utils/jest-koa-mocks/create-mock-context.js';
 
 import { checkRequiredProfile, signInWithPassword } from './utils.js';
 
diff --git a/packages/core/src/routes/setting.test.ts b/packages/core/src/routes/setting.test.ts
index 44c51087d..f3ccc1e61 100644
--- a/packages/core/src/routes/setting.test.ts
+++ b/packages/core/src/routes/setting.test.ts
@@ -1,20 +1,19 @@
 import type { Setting, CreateSetting } from '@logto/schemas';
+import { mockEsm, pickDefault } from '@logto/shared/esm';
 
 import { mockSetting } from '#src/__mocks__/index.js';
 import { createRequester } from '#src/utils/test-utils.js';
 
-import settingRoutes from './setting.js';
-
-jest.mock('#src/queries/setting.js', () => ({
-  getSetting: jest.fn(async (): Promise<Setting> => mockSetting),
-  updateSetting: jest.fn(
-    async (data: Partial<CreateSetting>): Promise<Setting> => ({
-      ...mockSetting,
-      ...data,
-    })
-  ),
+mockEsm('#src/queries/setting.js', () => ({
+  getSetting: async (): Promise<Setting> => mockSetting,
+  updateSetting: async (data: Partial<CreateSetting>): Promise<Setting> => ({
+    ...mockSetting,
+    ...data,
+  }),
 }));
 
+const settingRoutes = await pickDefault(import('./setting.js'));
+
 describe('settings routes', () => {
   const roleRequester = createRequester({ authedRoutes: settingRoutes });
 
diff --git a/packages/core/src/routes/sign-in-experience.branding.guard.test.ts b/packages/core/src/routes/sign-in-experience.branding.guard.test.ts
index f7aadbd4a..a0f2532ec 100644
--- a/packages/core/src/routes/sign-in-experience.branding.guard.test.ts
+++ b/packages/core/src/routes/sign-in-experience.branding.guard.test.ts
@@ -1,24 +1,24 @@
 import type { CreateSignInExperience, SignInExperience } from '@logto/schemas';
 import { BrandingStyle } from '@logto/schemas';
+import { mockEsm, mockEsmWithActual, pickDefault } from '@logto/shared/esm';
 
 import { mockBranding, mockSignInExperience } from '#src/__mocks__/index.js';
 import { createRequester } from '#src/utils/test-utils.js';
 
-import signInExperiencesRoutes from './sign-in-experience.js';
-
-jest.mock('#src/queries/sign-in-experience.js', () => ({
-  updateDefaultSignInExperience: jest.fn(
-    async (data: Partial<CreateSignInExperience>): Promise<SignInExperience> => ({
-      ...mockSignInExperience,
-      ...data,
-    })
-  ),
+await mockEsmWithActual('#src/queries/sign-in-experience.js', () => ({
+  updateDefaultSignInExperience: async (
+    data: Partial<CreateSignInExperience>
+  ): Promise<SignInExperience> => ({
+    ...mockSignInExperience,
+    ...data,
+  }),
 }));
 
-jest.mock('#src/connectors.js', () => ({
-  getLogtoConnectors: jest.fn(async () => []),
+mockEsm('#src/connectors.js', () => ({
+  getLogtoConnectors: async () => [],
 }));
 
+const signInExperiencesRoutes = await pickDefault(import('./sign-in-experience.js'));
 const signInExperienceRequester = createRequester({ authedRoutes: signInExperiencesRoutes });
 
 const expectPatchResponseStatus = async (
diff --git a/packages/core/src/routes/sign-in-experience.color.guard.test.ts b/packages/core/src/routes/sign-in-experience.color.guard.test.ts
index a254234a4..d6295c2ed 100644
--- a/packages/core/src/routes/sign-in-experience.color.guard.test.ts
+++ b/packages/core/src/routes/sign-in-experience.color.guard.test.ts
@@ -1,23 +1,23 @@
 import type { CreateSignInExperience, SignInExperience } from '@logto/schemas';
+import { mockEsm, mockEsmWithActual, pickDefault } from '@logto/shared/esm';
 
 import { mockColor, mockSignInExperience } from '#src/__mocks__/index.js';
 import { createRequester } from '#src/utils/test-utils.js';
 
-import signInExperiencesRoutes from './sign-in-experience.js';
-
-jest.mock('#src/queries/sign-in-experience.js', () => ({
-  updateDefaultSignInExperience: jest.fn(
-    async (data: Partial<CreateSignInExperience>): Promise<SignInExperience> => ({
-      ...mockSignInExperience,
-      ...data,
-    })
-  ),
+await mockEsmWithActual('#src/queries/sign-in-experience.js', () => ({
+  updateDefaultSignInExperience: async (
+    data: Partial<CreateSignInExperience>
+  ): Promise<SignInExperience> => ({
+    ...mockSignInExperience,
+    ...data,
+  }),
 }));
 
-jest.mock('#src/connectors.js', () => ({
-  getLogtoConnectors: jest.fn(async () => []),
+mockEsm('#src/connectors.js', () => ({
+  getLogtoConnectors: async () => [],
 }));
 
+const signInExperiencesRoutes = await pickDefault(import('./sign-in-experience.js'));
 const signInExperienceRequester = createRequester({ authedRoutes: signInExperiencesRoutes });
 
 const expectPatchResponseStatus = async (
diff --git a/packages/core/src/routes/sign-in-experience.guard.test.ts b/packages/core/src/routes/sign-in-experience.guard.test.ts
index b76b8df00..063ea6ce0 100644
--- a/packages/core/src/routes/sign-in-experience.guard.test.ts
+++ b/packages/core/src/routes/sign-in-experience.guard.test.ts
@@ -1,4 +1,5 @@
-import type { CreateSignInExperience, LanguageInfo, SignInExperience } from '@logto/schemas';
+import type { CreateSignInExperience, SignInExperience } from '@logto/schemas';
+import { mockEsm, mockEsmWithActual, pickDefault } from '@logto/shared/esm';
 
 import {
   mockAliyunDmConnector,
@@ -10,11 +11,10 @@ import {
   mockSignInExperience,
   mockTermsOfUse,
 } from '#src/__mocks__/index.js';
-import { createRequester } from '#src/utils/test-utils.js';
 
-import signInExperiencesRoutes from './sign-in-experience.js';
+const { jest } = import.meta;
 
-jest.mock('#src/connectors.js', () => ({
+mockEsm('#src/connectors.js', () => ({
   getLogtoConnectors: jest.fn(async () => [
     mockAliyunDmConnector,
     mockAliyunSmsConnector,
@@ -24,23 +24,21 @@ jest.mock('#src/connectors.js', () => ({
   ]),
 }));
 
-// eslint-disable-next-line @typescript-eslint/no-empty-function
-const validateLanguageInfo = jest.fn(async (languageInfo: LanguageInfo): Promise<void> => {});
-
-jest.mock('#src/lib/sign-in-experience.js', () => ({
-  ...jest.requireActual('#src/lib/sign-in-experience.js'),
-  validateLanguageInfo: async (languageInfo: LanguageInfo) => validateLanguageInfo(languageInfo),
+const { validateLanguageInfo } = await mockEsmWithActual('#src/lib/sign-in-experience.js', () => ({
+  validateLanguageInfo: jest.fn(),
 }));
 
-jest.mock('#src/queries/sign-in-experience.js', () => ({
-  updateDefaultSignInExperience: jest.fn(
-    async (data: Partial<CreateSignInExperience>): Promise<SignInExperience> => ({
-      ...mockSignInExperience,
-      ...data,
-    })
-  ),
+await mockEsmWithActual('#src/queries/sign-in-experience.js', () => ({
+  updateDefaultSignInExperience: async (
+    data: Partial<CreateSignInExperience>
+  ): Promise<SignInExperience> => ({
+    ...mockSignInExperience,
+    ...data,
+  }),
 }));
 
+const signInExperiencesRoutes = await pickDefault(import('./sign-in-experience.js'));
+const { createRequester } = await import('#src/utils/test-utils.js');
 const signInExperienceRequester = createRequester({ authedRoutes: signInExperiencesRoutes });
 
 const expectPatchResponseStatus = async (
diff --git a/packages/core/src/routes/sign-in-experience.test.ts b/packages/core/src/routes/sign-in-experience.test.ts
index 1cf33923d..9565d71c8 100644
--- a/packages/core/src/routes/sign-in-experience.test.ts
+++ b/packages/core/src/routes/sign-in-experience.test.ts
@@ -1,4 +1,5 @@
 import type { SignInExperience, CreateSignInExperience, TermsOfUse } from '@logto/schemas';
+import { mockEsm, mockEsmWithActual, pickDefault } from '@logto/shared/esm';
 
 import {
   mockFacebookConnector,
@@ -13,12 +14,23 @@ import {
   mockLanguageInfo,
   mockAliyunSmsConnector,
 } from '#src/__mocks__/index.js';
-import * as signInExpLib from '#src/lib/sign-in-experience/index.js';
-import * as signInLib from '#src/lib/sign-in-experience/sign-in.js';
-import * as signUpLib from '#src/lib/sign-in-experience/sign-up.js';
 import { createRequester } from '#src/utils/test-utils.js';
 
-import signInExperiencesRoutes from './sign-in-experience.js';
+const { jest } = import.meta;
+
+const {
+  validateBranding,
+  validateLanguageInfo,
+  validateTermsOfUse,
+  validateSignIn,
+  validateSignUp,
+} = await mockEsmWithActual('#src/lib/sign-in-experience/index.js', () => ({
+  validateBranding: jest.fn(),
+  validateLanguageInfo: jest.fn(),
+  validateTermsOfUse: jest.fn(),
+  validateSignIn: jest.fn(),
+  validateSignUp: jest.fn(),
+}));
 
 const logtoConnectors = [
   mockFacebookConnector,
@@ -28,33 +40,27 @@ const logtoConnectors = [
   mockAliyunSmsConnector,
 ];
 
-const getLogtoConnectors = jest.fn(async () => logtoConnectors);
-
-jest.mock('#src/connectors.js', () => {
-  return {
-    ...jest.requireActual('#src/connectors.js'),
-    getLogtoConnectors: jest.fn(async () => getLogtoConnectors()),
-  };
-});
-
-const findDefaultSignInExperience = jest.fn(async () => mockSignInExperience);
-
-jest.mock('#src/queries/sign-in-experience.js', () => ({
-  findDefaultSignInExperience: jest.fn(async () => findDefaultSignInExperience()),
-  updateDefaultSignInExperience: jest.fn(
-    async (data: Partial<CreateSignInExperience>): Promise<SignInExperience> => ({
-      ...mockSignInExperience,
-      ...data,
-    })
-  ),
+await mockEsmWithActual('#src/connectors.js', () => ({
+  getLogtoConnectors: async () => logtoConnectors,
 }));
 
-const signInExperienceRequester = createRequester({ authedRoutes: signInExperiencesRoutes });
+const { findDefaultSignInExperience } = mockEsm('#src/queries/sign-in-experience.js', () => ({
+  findDefaultSignInExperience: jest.fn(async () => mockSignInExperience),
+  updateDefaultSignInExperience: async (
+    data: Partial<CreateSignInExperience>
+  ): Promise<SignInExperience> => ({
+    ...mockSignInExperience,
+    ...data,
+  }),
+}));
 
-jest.mock('#src/queries/custom-phrase.js', () => ({
+mockEsm('#src/queries/custom-phrase.js', () => ({
   findAllCustomLanguageTags: async () => [],
 }));
 
+const signInExperiencesRoutes = await pickDefault(import('./sign-in-experience.js'));
+const signInExperienceRequester = createRequester({ authedRoutes: signInExperiencesRoutes });
+
 describe('GET /sign-in-exp', () => {
   afterAll(() => {
     jest.clearAllMocks();
@@ -103,12 +109,6 @@ describe('PATCH /sign-in-exp', () => {
     const termsOfUse: TermsOfUse = { enabled: false };
     const socialSignInConnectorTargets = ['github', 'facebook', 'wechat'];
 
-    const validateBranding = jest.spyOn(signInExpLib, 'validateBranding');
-    const validateLanguageInfo = jest.spyOn(signInExpLib, 'validateLanguageInfo');
-    const validateTermsOfUse = jest.spyOn(signInExpLib, 'validateTermsOfUse');
-    const validateSignIn = jest.spyOn(signInLib, 'validateSignIn');
-    const validateSignUp = jest.spyOn(signUpLib, 'validateSignUp');
-
     const response = await signInExperienceRequester.patch('/sign-in-exp').send({
       color: mockColor,
       branding: mockBranding,
diff --git a/packages/core/src/routes/swagger.test.ts b/packages/core/src/routes/swagger.test.ts
index f4353931b..4fb2103af 100644
--- a/packages/core/src/routes/swagger.test.ts
+++ b/packages/core/src/routes/swagger.test.ts
@@ -1,4 +1,4 @@
-import { load } from 'js-yaml';
+import { mockEsm } from '@logto/shared/esm';
 import Koa from 'koa';
 import Router from 'koa-router';
 import request from 'supertest';
@@ -8,12 +8,18 @@ import koaGuard from '#src/middleware/koa-guard.js';
 import koaPagination from '#src/middleware/koa-pagination.js';
 import type { AnonymousRouter } from '#src/routes/types.js';
 
-import swaggerRoutes, { defaultResponses, paginationParameters } from './swagger.js';
+const { jest } = import.meta;
 
-jest.mock('js-yaml', () => ({
+const { load } = mockEsm('js-yaml', () => ({
   load: jest.fn().mockReturnValue({ paths: {} }),
 }));
 
+const {
+  default: swaggerRoutes,
+  defaultResponses,
+  paginationParameters,
+} = await import('./swagger.js');
+
 export const createSwaggerRequest = (
   allRouters: Router[],
   swaggerRouter: AnonymousRouter = new Router()
@@ -221,7 +227,7 @@ describe('GET /swagger.json', () => {
 
   describe('should use correct responses', () => {
     it('should use "defaultResponses" if there is no custom "responses" from the additional swagger', async () => {
-      (load as jest.Mock).mockReturnValueOnce({
+      load.mockReturnValueOnce({
         paths: { '/api/mock': { delete: {} } },
       });
 
@@ -235,7 +241,7 @@ describe('GET /swagger.json', () => {
     });
 
     it('should use custom "responses" from the additional swagger if it exists', async () => {
-      (load as jest.Mock).mockReturnValueOnce({
+      load.mockReturnValueOnce({
         paths: {
           '/api/mock': {
             get: {
diff --git a/packages/core/src/routes/well-known.test.ts b/packages/core/src/routes/well-known.test.ts
index 532625d16..33c66db9d 100644
--- a/packages/core/src/routes/well-known.test.ts
+++ b/packages/core/src/routes/well-known.test.ts
@@ -3,7 +3,7 @@ import {
   adminConsoleApplicationId,
   adminConsoleSignInExperience,
 } from '@logto/schemas/lib/seeds/index.js';
-import { Provider } from 'oidc-provider';
+import { mockEsm, mockEsmWithActual, pickDefault } from '@logto/shared/esm';
 
 import {
   mockAliyunDmConnector,
@@ -15,50 +15,48 @@ import {
   mockWechatConnector,
   mockWechatNativeConnector,
 } from '#src/__mocks__/index.js';
-import * as signInExperienceQueries from '#src/queries/sign-in-experience.js';
-import wellKnownRoutes from '#src/routes/well-known.js';
+import { createMockProvider } from '#src/test-utils/oidc-provider.js';
 import { createRequester } from '#src/utils/test-utils.js';
 
-const getLogtoConnectors = jest.fn(async () => [
-  mockAliyunDmConnector,
-  mockAliyunSmsConnector,
-  mockFacebookConnector,
-  mockGithubConnector,
-  mockGoogleConnector,
-  mockWechatConnector,
-  mockWechatNativeConnector,
-]);
-
-jest.mock('#src/connectors.js', () => ({
-  getLogtoConnectors: async () => getLogtoConnectors(),
+const { jest } = import.meta;
+await mockEsmWithActual('i18next', () => ({
+  default: {
+    t: (key: string) => key,
+  },
 }));
 
-jest.mock('#src/queries/user.js', () => ({
+const { findDefaultSignInExperience } = mockEsm('#src/queries/sign-in-experience.js', () => ({
+  updateDefaultSignInExperience: jest.fn(),
+  findDefaultSignInExperience: jest.fn().mockResolvedValue(mockSignInExperience),
+}));
+
+await mockEsmWithActual('#src/queries/user.js', () => ({
   hasActiveUsers: jest.fn().mockResolvedValue(true),
 }));
 
-const interactionDetails: jest.MockedFunction<() => Promise<unknown>> = jest.fn(async () => ({
-  params: {},
+mockEsm('#src/connectors.js', () => ({
+  getLogtoConnectors: jest.fn(async () => [
+    mockAliyunDmConnector,
+    mockAliyunSmsConnector,
+    mockFacebookConnector,
+    mockGithubConnector,
+    mockGoogleConnector,
+    mockWechatConnector,
+    mockWechatNativeConnector,
+  ]),
 }));
 
-jest.mock('oidc-provider', () => ({
-  Provider: jest.fn(() => ({
-    interactionDetails,
-  })),
-}));
-
-jest.mock('i18next', () => ({
-  t: (key: string) => key,
-}));
+const wellKnownRoutes = await pickDefault(import('#src/routes/well-known.js'));
 
 describe('GET /.well-known/sign-in-exp', () => {
   afterEach(() => {
     jest.clearAllMocks();
   });
 
+  const provider = createMockProvider();
   const sessionRequest = createRequester({
     anonymousRoutes: wellKnownRoutes,
-    provider: new Provider(''),
+    provider,
     middlewares: [
       async (ctx, next) => {
         ctx.addLogContext = jest.fn();
@@ -69,13 +67,9 @@ describe('GET /.well-known/sign-in-exp', () => {
     ],
   });
 
-  const signInExperienceQuerySpyOn = jest
-    .spyOn(signInExperienceQueries, 'findDefaultSignInExperience')
-    .mockResolvedValue(mockSignInExperience);
-
   it('should return github and facebook connector instances', async () => {
     const response = await sessionRequest.get('/.well-known/sign-in-exp');
-    expect(signInExperienceQuerySpyOn).toHaveBeenCalledTimes(1);
+    expect(findDefaultSignInExperience).toHaveBeenCalledTimes(1);
     expect(response.status).toEqual(200);
     expect(response.body).toMatchObject({
       ...mockSignInExperience,
@@ -101,7 +95,10 @@ describe('GET /.well-known/sign-in-exp', () => {
   });
 
   it('should return admin console settings', async () => {
-    interactionDetails.mockResolvedValue({ params: { client_id: adminConsoleApplicationId } });
+    jest
+      .spyOn(provider, 'interactionDetails')
+      // @ts-expect-error for testing
+      .mockResolvedValue({ params: { client_id: adminConsoleApplicationId } });
     const response = await sessionRequest.get('/.well-known/sign-in-exp');
     expect(response.status).toEqual(200);
 
diff --git a/packages/core/src/test-utils/jest-koa-mocks/LICENSE b/packages/core/src/test-utils/jest-koa-mocks/LICENSE
new file mode 100644
index 000000000..9685c7522
--- /dev/null
+++ b/packages/core/src/test-utils/jest-koa-mocks/LICENSE
@@ -0,0 +1,9 @@
+MIT License
+
+Copyright (c) 2018-present Shopify
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/packages/core/src/test-utils/jest-koa-mocks/README.md b/packages/core/src/test-utils/jest-koa-mocks/README.md
new file mode 100644
index 000000000..a46306564
--- /dev/null
+++ b/packages/core/src/test-utils/jest-koa-mocks/README.md
@@ -0,0 +1,3 @@
+# jest-koa-mocks
+
+Updated from https://github.com/Shopify/quilt/tree/main/packages/jest-koa-mocks since ESM tests need explicit `const { jest } = import.meta;` to fetch the global jest object.
diff --git a/packages/core/src/test-utils/jest-koa-mocks/create-mock-context.ts b/packages/core/src/test-utils/jest-koa-mocks/create-mock-context.ts
new file mode 100644
index 000000000..cd8b0a93d
--- /dev/null
+++ b/packages/core/src/test-utils/jest-koa-mocks/create-mock-context.ts
@@ -0,0 +1,119 @@
+/* eslint-disable unicorn/no-abusive-eslint-disable */
+/* eslint-disable */
+import stream from 'stream';
+import { URL } from 'url';
+
+import type { Context } from 'koa';
+import Koa from 'koa';
+import type { RequestMethod } from 'node-mocks-http';
+import httpMocks from 'node-mocks-http';
+
+import type { MockCookies } from './create-mock-cookies.js';
+import createMockCookies from './create-mock-cookies.js';
+
+const { jest } = import.meta;
+
+export type Dictionary<T> = Record<string, T>;
+
+export type MockContext = {
+  cookies: MockCookies;
+  request: Context['request'] & {
+    body?: any;
+    rawBody?: string;
+    session?: any;
+  };
+} & Context;
+
+export type Options<CustomProperties extends Record<string, unknown>, RequestBody = undefined> = {
+  url?: string;
+  method?: RequestMethod;
+  statusCode?: number;
+  session?: Dictionary<any>;
+  headers?: Dictionary<string>;
+  cookies?: Dictionary<string>;
+  state?: Dictionary<any>;
+  encrypted?: boolean;
+  host?: string;
+  requestBody?: RequestBody;
+  rawBody?: string;
+  throw?: Function;
+  redirect?: Function;
+  customProperties?: CustomProperties;
+};
+
+export default function createContext<
+  CustomProperties extends Record<string, unknown>,
+  RequestBody = undefined
+>(options: Options<CustomProperties, RequestBody> = {}) {
+  const app = new Koa();
+
+  const {
+    cookies,
+    method,
+    statusCode,
+    session,
+    requestBody,
+    rawBody = '',
+    url = '',
+    host = 'test.com',
+    encrypted = false,
+    throw: throwFunction = jest.fn(),
+    redirect = jest.fn(),
+    headers = {},
+    state = {},
+    customProperties = {},
+  } = options;
+
+  const extensions = {
+    ...customProperties,
+    throw: throwFunction,
+    session,
+    redirect,
+    state,
+  };
+
+  const protocolFallback = encrypted ? 'https' : 'http';
+  const urlObject = new URL(url, `${protocolFallback}://${host}`);
+
+  const request = httpMocks.createRequest({
+    url: urlObject.toString(),
+    method,
+    statusCode,
+    session,
+    headers: {
+      // Koa determines protocol based on the `Host` header.
+      Host: urlObject.host,
+      ...headers,
+    },
+  });
+
+  // Some functions we call in the implementations will perform checks for `req.encrypted`, which delegates to the socket.
+  // MockRequest doesn't set a fake socket itself, so we create one here.
+  request.socket = new stream.Duplex() as any;
+  Object.defineProperty(request.socket, 'encrypted', {
+    writable: false,
+    value: urlObject.protocol === 'https:',
+  });
+
+  const res = httpMocks.createResponse();
+
+  // Koa sets a default status code of 404, not the node default of 200
+  // https://github.com/koajs/koa/blob/master/docs/api/response.md#responsestatus
+  res.statusCode = 404;
+
+  // This is to get around an odd behavior in the `cookies` library, where if `res.set` is defined, it will use an internal
+  // node function to set headers, which results in them being set in the wrong place.
+
+  res.set = undefined as any;
+
+  const context = app.createContext(request, res) as MockContext & CustomProperties;
+  Object.assign(context, extensions);
+  context.cookies = createMockCookies(cookies);
+
+  // Ctx.request.body is a common enough custom property for middleware to add that it's handy to just support it by default
+  context.request.body = requestBody;
+  context.request.rawBody = rawBody;
+
+  return context as Context;
+}
+/* eslint-enable */
diff --git a/packages/core/src/test-utils/jest-koa-mocks/create-mock-cookies.ts b/packages/core/src/test-utils/jest-koa-mocks/create-mock-cookies.ts
new file mode 100644
index 000000000..d4c9b185c
--- /dev/null
+++ b/packages/core/src/test-utils/jest-koa-mocks/create-mock-cookies.ts
@@ -0,0 +1,37 @@
+/* eslint-disable unicorn/no-abusive-eslint-disable */
+/* eslint-disable */
+import type { Context } from 'koa';
+
+const { jest } = import.meta;
+
+export type Cookies = Context['cookies'];
+
+export type Dictionary<T> = Record<string, T>;
+
+export type MockCookies = {
+  requestStore: Map<string, string>;
+  responseStore: Map<string, string>;
+} & Cookies;
+
+export default function createMockCookies(
+  cookies: Record<string, unknown> = {},
+  secure = true
+): MockCookies {
+  const cookieEntries = Object.keys(cookies).map((key) => [key, cookies[key]] as [string, string]);
+
+  const requestStore = new Map<string, string>(cookieEntries);
+  const responseStore = new Map<string, string>(cookieEntries);
+
+  return {
+    set: jest.fn((key, value) => {
+      return responseStore.set(key, value);
+    }),
+    get: jest.fn((key) => {
+      return requestStore.get(key);
+    }),
+    requestStore,
+    responseStore,
+    secure,
+  } as any;
+}
+/* eslint-enable */
diff --git a/packages/core/src/test-utils/oidc-provider.ts b/packages/core/src/test-utils/oidc-provider.ts
new file mode 100644
index 000000000..4893d602b
--- /dev/null
+++ b/packages/core/src/test-utils/oidc-provider.ts
@@ -0,0 +1,24 @@
+import { Provider } from 'oidc-provider';
+
+const { jest } = import.meta;
+
+export const createMockProvider = (interactionDetails?: jest.Mock): Provider => {
+  const originalWarn = console.warn;
+  const warn = jest.spyOn(console, 'warn').mockImplementation((...args) => {
+    // Disable while creating. Too many warnings.
+    if (typeof args[0] === 'string' && args[0].includes('oidc-provider')) {
+      return;
+    }
+
+    originalWarn(...args);
+  });
+  const provider = new Provider('https://logto.test');
+
+  warn.mockRestore();
+  jest.spyOn(provider, 'interactionDetails').mockImplementation(
+    // @ts-expect-error for testing
+    interactionDetails ?? (async () => ({ params: {}, jti: 'jti', client_id: 'mockApplicationId' }))
+  );
+
+  return provider;
+};
diff --git a/packages/core/src/utils/oidc-provider-event-listener.test.ts b/packages/core/src/utils/oidc-provider-event-listener.test.ts
index 3beefc51f..bea89da3c 100644
--- a/packages/core/src/utils/oidc-provider-event-listener.test.ts
+++ b/packages/core/src/utils/oidc-provider-event-listener.test.ts
@@ -9,36 +9,26 @@ import {
 } from '#src/utils/oidc-provider-event-listener.js';
 import { createContextWithRouteParameters } from '#src/utils/test-utils.js';
 
+const { jest } = import.meta;
+
 const userId = 'userIdValue';
 const sessionId = 'sessionIdValue';
 const applicationId = 'applicationIdValue';
 
 const addLogContext = jest.fn();
 const log = jest.fn();
-const addListener = jest.fn();
-
-jest.mock('oidc-provider', () => ({ Provider: jest.fn(() => ({ addListener })) }));
 
 describe('addOidcEventListeners', () => {
   afterEach(() => {
     jest.clearAllMocks();
   });
 
-  it('should add grantSuccessListener', () => {
-    const provider = new Provider('');
+  it('should add proper listeners', () => {
+    const provider = new Provider('https://logto.test');
+    const addListener = jest.spyOn(provider, 'addListener');
     addOidcEventListeners(provider);
     expect(addListener).toHaveBeenCalledWith('grant.success', grantSuccessListener);
-  });
-
-  it('should add grantErrorListener', () => {
-    const provider = new Provider('');
-    addOidcEventListeners(provider);
     expect(addListener).toHaveBeenCalledWith('grant.error', grantErrorListener);
-  });
-
-  it('should add grantRevokedListener', () => {
-    const provider = new Provider('');
-    addOidcEventListeners(provider);
     expect(addListener).toHaveBeenCalledWith('grant.revoked', grantRevokedListener);
   });
 });
diff --git a/packages/core/src/utils/test-utils.ts b/packages/core/src/utils/test-utils.ts
index d85f0c956..cf0a1dd49 100644
--- a/packages/core/src/utils/test-utils.ts
+++ b/packages/core/src/utils/test-utils.ts
@@ -1,5 +1,3 @@
-import type { Options } from '@shopify/jest-koa-mocks';
-import { createMockContext } from '@shopify/jest-koa-mocks';
 import type { MiddlewareType, Context, Middleware } from 'koa';
 import Koa from 'koa';
 import type { IRouterParamContext } from 'koa-router';
@@ -11,6 +9,8 @@ import type { PrimitiveValueExpression } from 'slonik/dist/src/types.js';
 import request from 'supertest';
 
 import type { AuthedRouter, AnonymousRouter } from '#src/routes/types.js';
+import type { Options } from '#src/test-utils/jest-koa-mocks/create-mock-context.js';
+import createMockContext from '#src/test-utils/jest-koa-mocks/create-mock-context.js';
 
 /**
  *  Slonik Query Mock Utils
diff --git a/packages/shared/package.json b/packages/shared/package.json
index 4dc3644d5..5548c608c 100644
--- a/packages/shared/package.json
+++ b/packages/shared/package.json
@@ -8,6 +8,14 @@
   "files": [
     "lib"
   ],
+  "exports": {
+    ".": {
+      "import": "./lib/index.js"
+    },
+    "./esm": {
+      "import": "./lib/esm/index.js"
+    }
+  },
   "publishConfig": {
     "access": "public"
   },
diff --git a/packages/shared/src/esm/index.ts b/packages/shared/src/esm/index.ts
new file mode 100644
index 000000000..c51c2b276
--- /dev/null
+++ b/packages/shared/src/esm/index.ts
@@ -0,0 +1,2 @@
+export { default as moduleProxy } from './module-proxy.js';
+export * from './mock-esm.js';
diff --git a/packages/shared/src/esm/mock-esm.ts b/packages/shared/src/esm/mock-esm.ts
new file mode 100644
index 000000000..05e251405
--- /dev/null
+++ b/packages/shared/src/esm/mock-esm.ts
@@ -0,0 +1,72 @@
+import path from 'path';
+
+const { jest } = import.meta;
+
+type MockParameters<T> = Parameters<(moduleName: string, factory: () => T) => void>;
+
+// See https://github.com/sindresorhus/callsites
+/* eslint-disable @silverhand/fp/no-mutation */
+const callSites = (): NodeJS.CallSite[] => {
+  const _prepareStackTrace = Error.prepareStackTrace;
+  Error.prepareStackTrace = (_, stack) => stack;
+  const stack = new Error().stack?.slice(1); // eslint-disable-line unicorn/error-message
+  Error.prepareStackTrace = _prepareStackTrace;
+
+  // @ts-expect-error ignore the error since it has been replaced with the original stack array
+  return stack ?? [];
+};
+/* eslint-enable @silverhand/fp/no-mutation */
+
+// Depth default is 2 since it'll be called by `mockEsmXyz()` in this module.
+// Need to trace one level deeper for the original caller.
+const resolvePath = (pathOrModule: string, depth = 2): string => {
+  if (pathOrModule === '@logto/shared') {
+    return new URL('../../', import.meta.url).pathname;
+  }
+
+  if (!pathOrModule.startsWith('.')) {
+    return pathOrModule;
+  }
+
+  return path.join(path.dirname(callSites()[depth]?.getFileName() ?? ''), pathOrModule);
+};
+
+export const mockEsmWithActual: <T>(...args: MockParameters<T>) => Promise<T> = async (
+  moduleName,
+  factory
+) => {
+  const resolvedModule = resolvePath(moduleName);
+  // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
+  const actual = await import(resolvedModule);
+  // eslint-disable-next-line @typescript-eslint/no-unsafe-return
+  jest.unstable_mockModule(resolvedModule, () => ({
+    ...actual,
+    ...factory(),
+  }));
+
+  // eslint-disable-next-line @typescript-eslint/no-unsafe-return
+  return import(resolvedModule);
+};
+
+export const mockEsm = <T>(...args: MockParameters<T>) => {
+  const mocked = args[1]();
+  jest.unstable_mockModule(resolvePath(args[0]), () => mocked);
+
+  return mocked;
+};
+
+export const mockEsmDefault = <T>(...args: MockParameters<T>) => {
+  const mocked = args[1]();
+
+  jest.unstable_mockModule(resolvePath(args[0]), () => ({ default: mocked }));
+
+  return mocked;
+};
+
+export const pickDefault = async <T extends Record<'default', unknown>>(
+  promise: Promise<T>
+): Promise<T['default']> => {
+  const awaited = await promise;
+
+  return awaited.default;
+};
diff --git a/packages/shared/src/utils/module-proxy.ts b/packages/shared/src/esm/module-proxy.ts
similarity index 91%
rename from packages/shared/src/utils/module-proxy.ts
rename to packages/shared/src/esm/module-proxy.ts
index e97f4bc9f..c378377d9 100644
--- a/packages/shared/src/utils/module-proxy.ts
+++ b/packages/shared/src/esm/module-proxy.ts
@@ -1,3 +1,4 @@
+const { jest } = import.meta;
 // For testing
 // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-assignment
 const proxy: ProxyConstructor = new Proxy<any>(
diff --git a/packages/shared/src/include.d/import-meta.d.ts b/packages/shared/src/include.d/import-meta.d.ts
new file mode 100644
index 000000000..e016debb5
--- /dev/null
+++ b/packages/shared/src/include.d/import-meta.d.ts
@@ -0,0 +1,10 @@
+interface ImportMeta {
+  jest: typeof jest & {
+    // Almost same as `jest.mock()`, but factory is required
+    unstable_mockModule: <T = unknown>(
+      moduleName: string,
+      factory: () => T,
+      options?: jest.MockOptions
+    ) => typeof jest;
+  };
+}
diff --git a/packages/shared/src/utils/index.ts b/packages/shared/src/utils/index.ts
index 7941ccd79..8bfcfa7f3 100644
--- a/packages/shared/src/utils/index.ts
+++ b/packages/shared/src/utils/index.ts
@@ -1,4 +1,3 @@
 export * from './id.js';
 export * from './function.js';
-export { default as moduleProxy } from './module-proxy.js';
 export { default as findPackage } from './find-package.js';
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index e1bf0e177..09663cb63 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -27,13 +27,13 @@ importers:
       '@logto/shared': workspace:*
       '@silverhand/eslint-config': 1.3.0
       '@silverhand/essentials': ^1.3.0
-      '@silverhand/jest-config': 1.2.2
       '@silverhand/ts-config': 1.2.1
       '@types/fs-extra': ^9.0.13
       '@types/inquirer': ^8.2.1
       '@types/jest': ^29.1.2
       '@types/node': ^16.0.0
       '@types/semver': ^7.3.12
+      '@types/sinon': ^10.0.13
       '@types/tar': ^6.1.2
       '@types/yargs': ^17.0.13
       chalk: ^5.0.0
@@ -47,17 +47,17 @@ importers:
       jest: ^29.3.1
       lint-staged: ^13.0.0
       nanoid: ^3.3.4
-      ora: ^5.0.0
+      ora: ^6.1.2
       p-retry: ^4.6.1
       prettier: ^2.7.1
       rimraf: ^3.0.2
       roarr: ^7.11.0
       semver: ^7.3.8
+      sinon: ^15.0.0
       slonik: ^30.0.0
       slonik-interceptor-preset: ^1.2.10
       slonik-sql-tag-raw: ^1.1.4
       tar: ^6.1.11
-      ts-node: ^10.9.1
       typescript: ^4.7.4
       yargs: ^17.6.0
       zod: ^3.19.1
@@ -73,7 +73,7 @@ importers:
       hpagent: 1.2.0
       inquirer: 8.2.2
       nanoid: 3.3.4
-      ora: 5.4.1
+      ora: 6.1.2
       p-retry: 4.6.1
       roarr: 7.11.0
       semver: 7.3.8
@@ -85,21 +85,21 @@ importers:
       zod: 3.19.1
     devDependencies:
       '@silverhand/eslint-config': 1.3.0_swk2g7ygmfleszo5c33j4vooni
-      '@silverhand/jest-config': 1.2.2_wkdujqsgbnfnnp5xidismkcn6e
       '@silverhand/ts-config': 1.2.1_typescript@4.7.4
       '@types/fs-extra': 9.0.13
       '@types/inquirer': 8.2.1
       '@types/jest': 29.1.2
       '@types/node': 16.11.12
       '@types/semver': 7.3.12
+      '@types/sinon': 10.0.13
       '@types/tar': 6.1.2
       '@types/yargs': 17.0.13
       eslint: 8.21.0
-      jest: 29.3.1_k5ytkvaprncdyzidqqws5bqksq
+      jest: 29.3.1_@types+node@16.11.12
       lint-staged: 13.0.0
       prettier: 2.7.1
       rimraf: 3.0.2
-      ts-node: 10.9.1_ccwudyfw5se7hgalwgkzhn2yp4
+      sinon: 15.0.0
       typescript: 4.7.4
 
   packages/console:
@@ -148,7 +148,7 @@ importers:
       lint-staged: ^13.0.0
       lodash.get: ^4.4.2
       lodash.kebabcase: ^4.1.1
-      nanoid: ^3.1.23
+      nanoid: ^3.3.4
       parcel: 2.8.0
       postcss: ^8.4.6
       postcss-modules: ^4.3.0
@@ -220,7 +220,7 @@ importers:
       lint-staged: 13.0.0
       lodash.get: 4.4.2
       lodash.kebabcase: 4.1.1
-      nanoid: 3.3.1
+      nanoid: 3.3.4
       parcel: 2.8.0_postcss@8.4.6
       postcss: 8.4.6
       postcss-modules: 4.3.0_postcss@8.4.6
@@ -258,10 +258,8 @@ importers:
       '@logto/phrases-ui': workspace:*
       '@logto/schemas': workspace:*
       '@logto/shared': workspace:*
-      '@shopify/jest-koa-mocks': ^5.0.1
       '@silverhand/eslint-config': 1.3.0
       '@silverhand/essentials': ^1.3.0
-      '@silverhand/jest-config': 1.2.2
       '@silverhand/ts-config': 1.2.1
       '@types/debug': ^4.1.7
       '@types/etag': ^1.8.1
@@ -277,6 +275,7 @@ importers:
       '@types/lodash.pick': ^4.4.6
       '@types/node': ^16.0.0
       '@types/oidc-provider': ^7.12.0
+      '@types/sinon': ^10.0.13
       '@types/supertest': ^2.0.11
       chalk: ^5.0.0
       clean-deep: ^3.4.0
@@ -308,8 +307,8 @@ importers:
       koa-send: ^5.0.1
       lint-staged: ^13.0.0
       lodash.pick: ^4.4.0
-      nanoid: ^3.1.23
-      nock: ^13.2.2
+      nanoid: ^3.3.4
+      node-mocks-http: ^1.12.1
       nodemon: ^2.0.19
       oidc-provider: ^7.13.0
       openapi-types: ^12.0.0
@@ -317,6 +316,7 @@ importers:
       prettier: ^2.7.1
       query-string: ^7.0.1
       roarr: ^7.11.0
+      sinon: ^15.0.0
       slonik: ^30.0.0
       slonik-interceptor-preset: ^1.2.10
       slonik-sql-tag-raw: ^1.1.4
@@ -359,7 +359,7 @@ importers:
       koa-router: 12.0.0
       koa-send: 5.0.1
       lodash.pick: 4.4.0
-      nanoid: 3.1.30
+      nanoid: 3.3.4
       oidc-provider: 7.13.0
       p-retry: 4.6.1
       query-string: 7.0.1
@@ -371,9 +371,7 @@ importers:
       snakecase-keys: 5.4.4
       zod: 3.19.1
     devDependencies:
-      '@shopify/jest-koa-mocks': 5.0.1
       '@silverhand/eslint-config': 1.3.0_xygfz6avl43ipur7dlp2av7gnm
-      '@silverhand/jest-config': 1.2.2_gxkpbehbojmgu22invxph4jlwq
       '@silverhand/ts-config': 1.2.1_typescript@4.9.3
       '@types/debug': 4.1.7
       '@types/etag': 1.8.1
@@ -389,6 +387,7 @@ importers:
       '@types/lodash.pick': 4.4.6
       '@types/node': 16.11.12
       '@types/oidc-provider': 7.12.0
+      '@types/sinon': 10.0.13
       '@types/supertest': 2.0.11
       copyfiles: 2.4.1
       eslint: 8.21.0
@@ -396,10 +395,11 @@ importers:
       jest: 29.1.2_@types+node@16.11.12
       jest-matcher-specific-error: 1.0.0
       lint-staged: 13.0.0
-      nock: 13.2.2
+      node-mocks-http: 1.12.1
       nodemon: 2.0.19
       openapi-types: 12.0.0
       prettier: 2.7.1
+      sinon: 15.0.0
       supertest: 6.2.2
       typescript: 4.9.3
 
@@ -1905,7 +1905,7 @@ packages:
       - ts-node
     dev: true
 
-  /@jest/core/29.3.1_ts-node@10.9.1:
+  /@jest/core/29.3.1:
     resolution: {integrity: sha512-0ohVjjRex985w5MmO5L3u5GR1O30DexhBSpuwx2P+9ftyqHdJXnk7IUWiP80oHMvt7ubHCJHxV0a0vlKVuZirw==}
     engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
     peerDependencies:
@@ -1926,7 +1926,7 @@ packages:
       exit: 0.1.2
       graceful-fs: 4.2.10
       jest-changed-files: 29.2.0
-      jest-config: 29.3.1_hvivgrlmkyd4vgu6rkkmg6acly
+      jest-config: 29.3.1_@types+node@17.0.23
       jest-haste-map: 29.3.1
       jest-message-util: 29.3.1
       jest-regex-util: 29.2.0
@@ -2466,7 +2466,7 @@ packages:
     dependencies:
       '@logto/language-kit': 1.0.0-beta.28_zod@3.19.1
       color: 4.2.3
-      nanoid: 3.3.1
+      nanoid: 3.3.4
       zod: 3.19.1
     dev: true
 
@@ -2478,7 +2478,7 @@ packages:
     dependencies:
       '@logto/language-kit': 1.0.0-beta.28_zod@3.19.1
       color: 4.2.3
-      nanoid: 3.3.1
+      nanoid: 3.3.4
       zod: 3.19.1
 
   /@logto/js/1.0.0-beta.13:
@@ -3469,16 +3469,6 @@ packages:
     resolution: {integrity: sha512-Yykovind6xzqAqd0t5umrdAGPlGLTE80cy80UkEnbt8Zv5zEYTFzJSNPQ81TY8BSpRreubu1oE54iHBv2UVnTQ==}
     dev: true
 
-  /@shopify/jest-koa-mocks/5.0.1:
-    resolution: {integrity: sha512-4YskS9q8+TEHNoyopmuoy2XyhInyqeOl7CF5ShJs19sm6m0EA/jGGvgf/osv2PeTfuf42/L2G9CzWUSg49yTSg==}
-    engines: {node: ^14.17.0 || >=16.0.0}
-    dependencies:
-      koa: 2.13.4
-      node-mocks-http: 1.11.0
-    transitivePeerDependencies:
-      - supports-color
-    dev: true
-
   /@sideway/address/4.1.4:
     resolution: {integrity: sha512-7vwq+rOHVWjyXxVlR76Agnvhy8I9rpzjosTESvmhNeXOXdZZB15Fl+TI9x1SiHZH5Jv2wTGduSxFDIaq0m3DUw==}
     dependencies:
@@ -3621,46 +3611,6 @@ packages:
       lodash.orderby: 4.6.0
       lodash.pick: 4.4.0
 
-  /@silverhand/jest-config/1.2.2_gxkpbehbojmgu22invxph4jlwq:
-    resolution: {integrity: sha512-sCOIHN3kIG9nyySkDao8nz6HK8VhGoUV4WG1CCriDDeGTqbHs4IprzTp1p+ChFdC8JGBCElQC0cIFrWYTFnTAQ==}
-    engines: {node: ^16.0.0 || ^18.0.0}
-    peerDependencies:
-      jest: ^29.0.0 || ^29.1.2
-    dependencies:
-      '@jest/types': 29.1.2
-      deepmerge: 4.2.2
-      identity-obj-proxy: 3.0.0
-      jest: 29.1.2_@types+node@16.11.12
-      jest-matcher-specific-error: 1.0.0
-      jest-transform-stub: 2.0.0
-      ts-jest: 29.0.3_lr7fqxhx6o7ex6ma5v5npbw6ae
-    transitivePeerDependencies:
-      - '@babel/core'
-      - babel-jest
-      - esbuild
-      - typescript
-    dev: true
-
-  /@silverhand/jest-config/1.2.2_wkdujqsgbnfnnp5xidismkcn6e:
-    resolution: {integrity: sha512-sCOIHN3kIG9nyySkDao8nz6HK8VhGoUV4WG1CCriDDeGTqbHs4IprzTp1p+ChFdC8JGBCElQC0cIFrWYTFnTAQ==}
-    engines: {node: ^16.0.0 || ^18.0.0}
-    peerDependencies:
-      jest: ^29.0.0 || ^29.1.2
-    dependencies:
-      '@jest/types': 29.1.2
-      deepmerge: 4.2.2
-      identity-obj-proxy: 3.0.0
-      jest: 29.3.1_k5ytkvaprncdyzidqqws5bqksq
-      jest-matcher-specific-error: 1.0.0
-      jest-transform-stub: 2.0.0
-      ts-jest: 29.0.3_o3wtcjdhyxuv43bggxcaucanwu
-    transitivePeerDependencies:
-      - '@babel/core'
-      - babel-jest
-      - esbuild
-      - typescript
-    dev: true
-
   /@silverhand/jest-config/1.2.2_zapogttls25djihwjkusccjjym:
     resolution: {integrity: sha512-sCOIHN3kIG9nyySkDao8nz6HK8VhGoUV4WG1CCriDDeGTqbHs4IprzTp1p+ChFdC8JGBCElQC0cIFrWYTFnTAQ==}
     engines: {node: ^16.0.0 || ^18.0.0}
@@ -3728,6 +3678,18 @@ packages:
       type-detect: 4.0.8
     dev: true
 
+  /@sinonjs/commons/2.0.0:
+    resolution: {integrity: sha512-uLa0j859mMrg2slwQYdO/AkrOfmH+X6LTVmNTS9CqexuE2IvVORIkSpJLqePAbEnKJ77aMmCwr1NUZ57120Xcg==}
+    dependencies:
+      type-detect: 4.0.8
+    dev: true
+
+  /@sinonjs/fake-timers/7.1.2:
+    resolution: {integrity: sha512-iQADsW4LBMISqZ6Ci1dupJL9pprqwcVFTcOsEmQOEhW+KLCVn/Y4Jrvg2k19fIHCp+iFprriYPTdRcQR8NbUPg==}
+    dependencies:
+      '@sinonjs/commons': 1.8.3
+    dev: true
+
   /@sinonjs/fake-timers/8.1.0:
     resolution: {integrity: sha512-OAPJUAtgeINhh/TAlUID4QTs53Njm7xzddaVlEs/SXwgtiD1tW22zAB/W1wdqfrpmikgaWQ9Fw6Ws+hsiRm5Vg==}
     dependencies:
@@ -3740,6 +3702,18 @@ packages:
       '@sinonjs/commons': 1.8.3
     dev: true
 
+  /@sinonjs/samsam/7.0.1:
+    resolution: {integrity: sha512-zsAk2Jkiq89mhZovB2LLOdTCxJF4hqqTToGP0ASWlhp4I1hqOjcfmZGafXntCN7MDC6yySH0mFHrYtHceOeLmw==}
+    dependencies:
+      '@sinonjs/commons': 2.0.0
+      lodash.get: 4.4.2
+      type-detect: 4.0.8
+    dev: true
+
+  /@sinonjs/text-encoding/0.7.2:
+    resolution: {integrity: sha512-sXXKG+uL9IrKqViTtao2Ws6dy0znu9sOaP1di/jKGW1M6VssO8vlpXCQcpZ+jisQ1tTFAC5Jo/EOzFbggBagFQ==}
+    dev: true
+
   /@svgr/babel-plugin-add-jsx-attribute/6.5.1_@babel+core@7.20.2:
     resolution: {integrity: sha512-9PYGcXrAxitycIjRmZB+Q0JaN07GZIWaTBIGQzfaZv+qr1n8X1XUEJ5rZ/vx6OVD9RRYlrNnXWExQXcmZeD/BQ==}
     engines: {node: '>=10'}
@@ -4416,6 +4390,16 @@ packages:
       '@types/node': 17.0.23
     dev: true
 
+  /@types/sinon/10.0.13:
+    resolution: {integrity: sha512-UVjDqJblVNQYvVNUsj0PuYYw0ELRmgt1Nt5Vk0pT5f16ROGfcKJY8o1HVuMOJOpD727RrGB9EGvoaTQE5tgxZQ==}
+    dependencies:
+      '@types/sinonjs__fake-timers': 8.1.2
+    dev: true
+
+  /@types/sinonjs__fake-timers/8.1.2:
+    resolution: {integrity: sha512-9GcLXF0/v3t80caGs5p2rRfkB+a8VBGLJZVih6CNFkx8IZ994wiKKLSRs9nuFwk1HevWs/1mnUmkApGrSGsShA==}
+    dev: true
+
   /@types/stack-utils/2.0.1:
     resolution: {integrity: sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==}
     dev: true
@@ -4741,6 +4725,7 @@ packages:
     dependencies:
       mime-types: 2.1.35
       negotiator: 0.6.2
+    dev: false
 
   /accepts/1.3.8:
     resolution: {integrity: sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==}
@@ -4848,7 +4833,6 @@ packages:
   /ansi-regex/6.0.1:
     resolution: {integrity: sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==}
     engines: {node: '>=12'}
-    dev: true
 
   /ansi-styles/3.2.1:
     resolution: {integrity: sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==}
@@ -5196,6 +5180,14 @@ packages:
       inherits: 2.0.4
       readable-stream: 3.6.0
 
+  /bl/5.1.0:
+    resolution: {integrity: sha512-tv1ZJHLfTDnXE6tMHv73YgSJaWR2AFuPwMntBe7XL/GBFHnT0CLnsHMogfk5+GzCDC5ZWarSCYaIGATZt9dNsQ==}
+    dependencies:
+      buffer: 6.0.3
+      inherits: 2.0.4
+      readable-stream: 3.6.0
+    dev: false
+
   /bluebird/3.7.2:
     resolution: {integrity: sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==}
 
@@ -5266,6 +5258,13 @@ packages:
       base64-js: 1.5.1
       ieee754: 1.2.1
 
+  /buffer/6.0.3:
+    resolution: {integrity: sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==}
+    dependencies:
+      base64-js: 1.5.1
+      ieee754: 1.2.1
+    dev: false
+
   /bufferput/0.1.3:
     resolution: {integrity: sha512-nmPV88vDNzf0VMU1bdQ4A1oBlRR9y+CXfwWKfyKUgI2ZIkvreNzLMM3tkz0Lapb6f+Cz1V001UWRBsoGVCjqdw==}
     engines: {node: '>=0.3.0'}
@@ -5291,6 +5290,7 @@ packages:
     dependencies:
       mime-types: 2.1.35
       ylru: 1.2.1
+    dev: false
 
   /cacheable-lookup/5.0.4:
     resolution: {integrity: sha512-2/kNscPhpcxrOigMZzbiWF7dz8ilhb/nIHU3EyZiXWXpeq/au8qJ8VhdftMkty3n7Gj6HIGalQG8oiBNB3AJgA==}
@@ -5512,6 +5512,13 @@ packages:
     dependencies:
       restore-cursor: 3.1.0
 
+  /cli-cursor/4.0.0:
+    resolution: {integrity: sha512-VGtlMu3x/4DOtIUwEkRezxUZ2lBacNJCHash0N0WeZDBS+7Ux1dm3XWAgWYxLJFMMdOeXMHXorshEFhbMSGelg==}
+    engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
+    dependencies:
+      restore-cursor: 4.0.0
+    dev: false
+
   /cli-spinners/2.6.1:
     resolution: {integrity: sha512-x/5fWmGMnbKQAaNwN+UZlV79qBLM9JFnJuJ03gIi5whrob0xV0ofNVHy9DhwGdsMJQc2OKv0oGmLzvaqvAVv+g==}
     engines: {node: '>=6'}
@@ -5723,6 +5730,7 @@ packages:
   /content-type/1.0.4:
     resolution: {integrity: sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==}
     engines: {node: '>= 0.6'}
+    dev: false
 
   /conventional-changelog-angular/5.0.13:
     resolution: {integrity: sha512-i/gipMxs7s8L/QeuavPF2hLnJgH6pEZAttySB6aiQLWcX3puWDL3ACVmvBhJGxnAy52Qc15ua26BufY6KpmrVA==}
@@ -5772,6 +5780,7 @@ packages:
     dependencies:
       depd: 2.0.0
       keygrip: 1.1.0
+    dev: false
 
   /copyfiles/2.4.1:
     resolution: {integrity: sha512-fereAvAvxDrQDOXybk3Qu3dPbOoKoysFMWtkY3mv5BsL8//OSZVL5DCLYqgRfY5cWirgRzlC+WSrxp6Bo3eNZg==}
@@ -6107,18 +6116,6 @@ packages:
       supports-color: 5.5.0
     dev: true
 
-  /debug/4.3.3:
-    resolution: {integrity: sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==}
-    engines: {node: '>=6.0'}
-    peerDependencies:
-      supports-color: '*'
-    peerDependenciesMeta:
-      supports-color:
-        optional: true
-    dependencies:
-      ms: 2.1.2
-    dev: true
-
   /debug/4.3.4:
     resolution: {integrity: sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==}
     engines: {node: '>=6.0'}
@@ -6179,6 +6176,7 @@ packages:
 
   /deep-equal/1.0.1:
     resolution: {integrity: sha1-9dJgKStmDghO/0zbyfCK0yR0SLU=}
+    dev: false
 
   /deep-is/0.1.4:
     resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==}
@@ -6232,6 +6230,7 @@ packages:
 
   /delegates/1.0.0:
     resolution: {integrity: sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=}
+    dev: false
 
   /depd/1.1.2:
     resolution: {integrity: sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==}
@@ -6240,6 +6239,7 @@ packages:
   /depd/2.0.0:
     resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==}
     engines: {node: '>= 0.8'}
+    dev: false
 
   /dequal/2.0.2:
     resolution: {integrity: sha512-q9K8BlJVxK7hQYqa6XISGmBZbtQQWVXSrRrWreHC94rMt1QL/Impruc+7p2CYSYuVIUr+YCt6hjrs1kkdJRTug==}
@@ -6248,6 +6248,7 @@ packages:
 
   /destroy/1.0.4:
     resolution: {integrity: sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=}
+    dev: false
 
   /detab/2.0.4:
     resolution: {integrity: sha512-8zdsQA5bIkoRECvCrNKPla84lyoR7DSAyf7p0YgXzBO9PDJx8KntPUay7NS6yp+KdxdVtiE5SpHKtbp2ZQyA9g==}
@@ -6410,6 +6411,7 @@ packages:
 
   /ee-first/1.1.1:
     resolution: {integrity: sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=}
+    dev: false
 
   /ejs/3.1.8:
     resolution: {integrity: sha512-/sXZeMlhS0ArkfX2Aw780gJzXSMPnKjtspYZv+f3NiKLlubezAHDU5+9xz6gd3/NhG3txQCo6xlglmTS+oTGEQ==}
@@ -6447,6 +6449,7 @@ packages:
   /encodeurl/1.0.2:
     resolution: {integrity: sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=}
     engines: {node: '>= 0.8'}
+    dev: false
 
   /end-of-stream/1.4.4:
     resolution: {integrity: sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==}
@@ -6541,6 +6544,7 @@ packages:
 
   /escape-html/1.0.3:
     resolution: {integrity: sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=}
+    dev: false
 
   /escape-string-regexp/1.0.5:
     resolution: {integrity: sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==}
@@ -8048,6 +8052,7 @@ packages:
     dependencies:
       deep-equal: 1.0.1
       http-errors: 1.8.1
+    dev: false
 
   /http-cache-semantics/4.1.0:
     resolution: {integrity: sha512-carPklcUh7ROWRK7Cv27RPtdhYhUsela/ue5/jKzjegVvXDqM2ILE9Q2BGn9JZJh1g87cp56su/FgQSzcWS8cQ==}
@@ -8460,6 +8465,7 @@ packages:
     engines: {node: '>= 0.4'}
     dependencies:
       has-tostringtag: 1.0.0
+    dev: false
 
   /is-get-set-prop/1.0.0:
     resolution: {integrity: sha512-DvAYZ1ZgGUz4lzxKMPYlt08qAUqyG9ckSg2pIjfvcQ7+pkVNUHk8yVLXOnCLe5WKXhLop8oorWFBJHpwWQpszQ==}
@@ -8484,6 +8490,11 @@ packages:
     engines: {node: '>=8'}
     dev: false
 
+  /is-interactive/2.0.0:
+    resolution: {integrity: sha512-qP1vozQRI+BMOPcjFzrjXuQvdak2pHNUMZoeG2eRbiSqyvbEf/wQtEOTOX1guk6E3t36RkaqiSt8A/6YElNxLQ==}
+    engines: {node: '>=12'}
+    dev: false
+
   /is-js-type/2.0.0:
     resolution: {integrity: sha512-Aj13l47+uyTjlQNHtXBV8Cji3jb037vxwMWCgopRR8h6xocgBGW3qG8qGlIOEmbXQtkKShKuBM9e8AA1OeQ+xw==}
     dependencies:
@@ -8622,6 +8633,11 @@ packages:
     engines: {node: '>=10'}
     dev: false
 
+  /is-unicode-supported/1.3.0:
+    resolution: {integrity: sha512-43r2mRvz+8JRIKnWJ+3j8JtjRKZ6GmjzfaE/qiBJnikNnYv/6bagRJ1kUhNk8R5EX/GkobD+r+sfxCPJsiKBLQ==}
+    engines: {node: '>=12'}
+    dev: false
+
   /is-weakref/1.0.2:
     resolution: {integrity: sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==}
     dependencies:
@@ -8654,7 +8670,7 @@ packages:
     dev: true
 
   /isarray/0.0.1:
-    resolution: {integrity: sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=}
+    resolution: {integrity: sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==}
 
   /isarray/1.0.0:
     resolution: {integrity: sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==}
@@ -8883,7 +8899,7 @@ packages:
       - ts-node
     dev: true
 
-  /jest-cli/29.3.1_k5ytkvaprncdyzidqqws5bqksq:
+  /jest-cli/29.3.1_@types+node@16.11.12:
     resolution: {integrity: sha512-TO/ewvwyvPOiBBuWZ0gm04z3WWP8TIK8acgPzE4IxgsLKQgb377NYGrQLc3Wl/7ndWzIH2CDNNsUjGxwLL43VQ==}
     engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
     hasBin: true
@@ -8893,14 +8909,14 @@ packages:
       node-notifier:
         optional: true
     dependencies:
-      '@jest/core': 29.3.1_ts-node@10.9.1
+      '@jest/core': 29.3.1
       '@jest/test-result': 29.3.1
       '@jest/types': 29.3.1
       chalk: 4.1.2
       exit: 0.1.2
       graceful-fs: 4.2.10
       import-local: 3.1.0
-      jest-config: 29.3.1_k5ytkvaprncdyzidqqws5bqksq
+      jest-config: 29.3.1_@types+node@16.11.12
       jest-util: 29.3.1
       jest-validate: 29.3.1
       prompts: 2.4.2
@@ -9107,47 +9123,7 @@ packages:
       - supports-color
     dev: true
 
-  /jest-config/29.3.1_hvivgrlmkyd4vgu6rkkmg6acly:
-    resolution: {integrity: sha512-y0tFHdj2WnTEhxmGUK1T7fgLen7YK4RtfvpLFBXfQkh2eMJAQq24Vx9472lvn5wg0MAO6B+iPfJfzdR9hJYalg==}
-    engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
-    peerDependencies:
-      '@types/node': '*'
-      ts-node: '>=9.0.0'
-    peerDependenciesMeta:
-      '@types/node':
-        optional: true
-      ts-node:
-        optional: true
-    dependencies:
-      '@babel/core': 7.19.3
-      '@jest/test-sequencer': 29.3.1
-      '@jest/types': 29.3.1
-      '@types/node': 17.0.23
-      babel-jest: 29.3.1_@babel+core@7.19.3
-      chalk: 4.1.2
-      ci-info: 3.5.0
-      deepmerge: 4.2.2
-      glob: 7.2.3
-      graceful-fs: 4.2.10
-      jest-circus: 29.3.1
-      jest-environment-node: 29.3.1
-      jest-get-type: 29.2.0
-      jest-regex-util: 29.2.0
-      jest-resolve: 29.3.1
-      jest-runner: 29.3.1
-      jest-util: 29.3.1
-      jest-validate: 29.3.1
-      micromatch: 4.0.5
-      parse-json: 5.2.0
-      pretty-format: 29.3.1
-      slash: 3.0.0
-      strip-json-comments: 3.1.1
-      ts-node: 10.9.1_ccwudyfw5se7hgalwgkzhn2yp4
-    transitivePeerDependencies:
-      - supports-color
-    dev: true
-
-  /jest-config/29.3.1_k5ytkvaprncdyzidqqws5bqksq:
+  /jest-config/29.3.1_@types+node@16.11.12:
     resolution: {integrity: sha512-y0tFHdj2WnTEhxmGUK1T7fgLen7YK4RtfvpLFBXfQkh2eMJAQq24Vx9472lvn5wg0MAO6B+iPfJfzdR9hJYalg==}
     engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
     peerDependencies:
@@ -9182,7 +9158,45 @@ packages:
       pretty-format: 29.3.1
       slash: 3.0.0
       strip-json-comments: 3.1.1
-      ts-node: 10.9.1_ccwudyfw5se7hgalwgkzhn2yp4
+    transitivePeerDependencies:
+      - supports-color
+    dev: true
+
+  /jest-config/29.3.1_@types+node@17.0.23:
+    resolution: {integrity: sha512-y0tFHdj2WnTEhxmGUK1T7fgLen7YK4RtfvpLFBXfQkh2eMJAQq24Vx9472lvn5wg0MAO6B+iPfJfzdR9hJYalg==}
+    engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
+    peerDependencies:
+      '@types/node': '*'
+      ts-node: '>=9.0.0'
+    peerDependenciesMeta:
+      '@types/node':
+        optional: true
+      ts-node:
+        optional: true
+    dependencies:
+      '@babel/core': 7.19.3
+      '@jest/test-sequencer': 29.3.1
+      '@jest/types': 29.3.1
+      '@types/node': 17.0.23
+      babel-jest: 29.3.1_@babel+core@7.19.3
+      chalk: 4.1.2
+      ci-info: 3.5.0
+      deepmerge: 4.2.2
+      glob: 7.2.3
+      graceful-fs: 4.2.10
+      jest-circus: 29.3.1
+      jest-environment-node: 29.3.1
+      jest-get-type: 29.2.0
+      jest-regex-util: 29.2.0
+      jest-resolve: 29.3.1
+      jest-runner: 29.3.1
+      jest-util: 29.3.1
+      jest-validate: 29.3.1
+      micromatch: 4.0.5
+      parse-json: 5.2.0
+      pretty-format: 29.3.1
+      slash: 3.0.0
+      strip-json-comments: 3.1.1
     transitivePeerDependencies:
       - supports-color
     dev: true
@@ -9965,7 +9979,7 @@ packages:
       - ts-node
     dev: true
 
-  /jest/29.3.1_k5ytkvaprncdyzidqqws5bqksq:
+  /jest/29.3.1_@types+node@16.11.12:
     resolution: {integrity: sha512-6iWfL5DTT0Np6UYs/y5Niu7WIfNv/wRTtN5RSXt2DIEft3dx3zPuw/3WJQBCJfmEzvDiEKwoqMbGD9n49+qLSA==}
     engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
     hasBin: true
@@ -9975,10 +9989,10 @@ packages:
       node-notifier:
         optional: true
     dependencies:
-      '@jest/core': 29.3.1_ts-node@10.9.1
+      '@jest/core': 29.3.1
       '@jest/types': 29.3.1
       import-local: 3.1.0
-      jest-cli: 29.3.1_k5ytkvaprncdyzidqqws5bqksq
+      jest-cli: 29.3.1_@types+node@16.11.12
     transitivePeerDependencies:
       - '@types/node'
       - supports-color
@@ -10101,6 +10115,7 @@ packages:
 
   /json-stringify-safe/5.0.1:
     resolution: {integrity: sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==}
+    dev: false
 
   /json5/1.0.1:
     resolution: {integrity: sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==}
@@ -10141,11 +10156,16 @@ packages:
       object.assign: 4.1.4
     dev: true
 
+  /just-extend/4.2.1:
+    resolution: {integrity: sha512-g3UB796vUFIY90VIv/WX3L2c8CS2MdWUww3CNrYmqza1Fg0DURc2K/O4YrnklBdQarSJ/y8JnJYDGc+1iumQjg==}
+    dev: true
+
   /keygrip/1.1.0:
     resolution: {integrity: sha512-iYSchDJ+liQ8iwbSI2QqsQOvqv58eJCEanyJPJi+Khyu8smkcKSFUCbPwzFcL7YVtZ6eONjqRX/38caJ7QjRAQ==}
     engines: {node: '>= 0.6'}
     dependencies:
       tsscmp: 1.0.6
+    dev: false
 
   /keyv/4.0.4:
     resolution: {integrity: sha512-vqNHbAc8BBsxk+7QBYLW0Y219rWcClspR6WSeoHYKG5mnsSoOH+BL1pWq02DDCVdvvuUny5rkBlzMRzoqc+GIg==}
@@ -10201,6 +10221,7 @@ packages:
 
   /koa-compose/4.1.0:
     resolution: {integrity: sha512-8ODW8TrDuMYvXRwra/Kh7/rJo9BtOfPc6qO8eAfC80CnCvSjSl0bkRM24X6/XBBEyj0v1nRUQ1LyOy3dbqOWXw==}
+    dev: false
 
   /koa-convert/2.0.0:
     resolution: {integrity: sha512-asOvN6bFlSnxewce2e/DK3p4tltyfC4VM7ZwuTuepI7dEQVcvpyFuBcEARu1+Hxg8DIwytce2n7jrZtRlPrARA==}
@@ -10208,6 +10229,7 @@ packages:
     dependencies:
       co: 4.6.0
       koa-compose: 4.1.0
+    dev: false
 
   /koa-logger/3.2.1:
     resolution: {integrity: sha512-MjlznhLLKy9+kG8nAXKJLM0/ClsQp/Or2vI3a5rbSQmgl8IJBQO0KI5FA70BvW+hqjtxjp49SpH2E7okS6NmHg==}
@@ -10291,6 +10313,7 @@ packages:
       vary: 1.1.2
     transitivePeerDependencies:
       - supports-color
+    dev: false
 
   /ky/0.32.2:
     resolution: {integrity: sha512-eBJeF6IXNwX5rksdwBrE2rIJrU2d84GoTvdM7OmmTIwUVXEMd72wIwvT+nyhrqtv7AzbSNsWz7yRsHgVhj1uog==}
@@ -10562,10 +10585,6 @@ packages:
   /lodash.pick/4.4.0:
     resolution: {integrity: sha512-hXt6Ul/5yWjfklSGvLQl8vM//l3FtyHZeuelpzK6mm99pNvN9yTDruNZPEJZD1oWrqo+izBmB7oUfWgcCX7s4Q==}
 
-  /lodash.set/4.3.2:
-    resolution: {integrity: sha1-2HV7HagH3eJIFrDWqEvqGnYjCyM=}
-    dev: true
-
   /lodash.sortby/4.7.0:
     resolution: {integrity: sha512-HDWXG8isMntAyRF5vZ7xKuEvOhT4AhlRt/3czTSjvGUxjYCBVRQY48ViDHyfYz9VIoBkW4TMGQNapx+l3RUwdA==}
 
@@ -10599,6 +10618,14 @@ packages:
       is-unicode-supported: 0.1.0
     dev: false
 
+  /log-symbols/5.1.0:
+    resolution: {integrity: sha512-l0x2DvrW294C9uDCoQe1VSU4gf529FkSZ6leBl4TiqZH/e+0R7hSfHQBNut2mNygDgHwvYHfFLn6Oxb3VWj2rA==}
+    engines: {node: '>=12'}
+    dependencies:
+      chalk: 5.1.2
+      is-unicode-supported: 1.3.0
+    dev: false
+
   /log-update/4.0.0:
     resolution: {integrity: sha512-9fkkDevMefjg0mmzWFBW8YkFP91OrizzkW3diF7CpG+S2EYdy4+TVfGwz1zeF8x7hCx1ovSPTOE9Ngib74qqUg==}
     engines: {node: '>=10'}
@@ -11339,17 +11366,6 @@ packages:
     resolution: {integrity: sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==}
     dev: false
 
-  /nanoid/3.1.30:
-    resolution: {integrity: sha512-zJpuPDwOv8D2zq2WRoMe1HsfZthVewpel9CAvTfc/2mBD1uUT/agc5f7GHGWXlYkFvi1mVxe4IjvP2HNrop7nQ==}
-    engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
-    hasBin: true
-    dev: false
-
-  /nanoid/3.3.1:
-    resolution: {integrity: sha512-n6Vs/3KGyxPQd6uO0eH4Bv0ojGSUvuLlIHtC3Y0kEO23YRge8H9x1GCzLn28YX0H66pMkxuaeESFq4tKISKwdw==}
-    engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
-    hasBin: true
-
   /nanoid/3.3.4:
     resolution: {integrity: sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==}
     engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
@@ -11362,30 +11378,29 @@ packages:
   /negotiator/0.6.2:
     resolution: {integrity: sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==}
     engines: {node: '>= 0.6'}
+    dev: false
 
   /negotiator/0.6.3:
     resolution: {integrity: sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==}
     engines: {node: '>= 0.6'}
     dev: true
 
+  /nise/5.1.3:
+    resolution: {integrity: sha512-U597iWTTBBYIV72986jyU382/MMZ70ApWcRmkoF1AZ75bpqOtI3Gugv/6+0jLgoDOabmcSwYBkSSAWIp1eA5cg==}
+    dependencies:
+      '@sinonjs/commons': 2.0.0
+      '@sinonjs/fake-timers': 7.1.2
+      '@sinonjs/text-encoding': 0.7.2
+      just-extend: 4.2.1
+      path-to-regexp: 1.8.0
+    dev: true
+
   /no-case/3.0.4:
     resolution: {integrity: sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==}
     dependencies:
       lower-case: 2.0.2
       tslib: 2.4.0
 
-  /nock/13.2.2:
-    resolution: {integrity: sha512-PcBHuvl9i6zfaJ50A7LS55oU+nFLv8htXIhffJO+FxyfibdZ4jEvd9kTuvkrJireBFIGMZ+oUIRpMK5gU9h//g==}
-    engines: {node: '>= 10.13'}
-    dependencies:
-      debug: 4.3.3
-      json-stringify-safe: 5.0.1
-      lodash.set: 4.3.2
-      propagate: 2.0.1
-    transitivePeerDependencies:
-      - supports-color
-    dev: true
-
   /node-addon-api/3.2.1:
     resolution: {integrity: sha512-mmcei9JghVNDYydghQmeDX8KoAm0FAiYyIcUt/N4nhyAipB17pllZQDOJD2fotxABnt4Mdz+dKTO7eftLg4d0A==}
     dev: true
@@ -11420,8 +11435,8 @@ packages:
     resolution: {integrity: sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==}
     dev: true
 
-  /node-mocks-http/1.11.0:
-    resolution: {integrity: sha512-jS/WzSOcKbOeGrcgKbenZeNhxUNnP36Yw11+hL4TTxQXErGfqYZ+MaYNNvhaTiGIJlzNSqgQkk9j8dSu1YWSuw==}
+  /node-mocks-http/1.12.1:
+    resolution: {integrity: sha512-jrA7Sn3qI6GsHgWtUW3gMj0vO6Yz0nJjzg3jRZYjcfj4tzi8oWPauDK1qHVJoAxTbwuDHF1JiM9GISZ/ocI/ig==}
     engines: {node: '>=0.6'}
     dependencies:
       accepts: 1.3.8
@@ -11637,6 +11652,7 @@ packages:
     engines: {node: '>= 0.8'}
     dependencies:
       ee-first: 1.1.1
+    dev: false
 
   /once/1.4.0:
     resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==}
@@ -11658,6 +11674,7 @@ packages:
 
   /only/0.0.2:
     resolution: {integrity: sha1-Kv3oTQPlC5qO3EROMGEKcCle37Q=}
+    dev: false
 
   /open/8.4.0:
     resolution: {integrity: sha512-XgFPPM+B28FtCCgSb9I+s9szOC1vZRSwgWsRUA5ylIxRTgKozqjOCrVOqGsYABPYK5qnfqClxZTFBa8PKt2v6Q==}
@@ -11720,6 +11737,21 @@ packages:
       wcwidth: 1.0.1
     dev: false
 
+  /ora/6.1.2:
+    resolution: {integrity: sha512-EJQ3NiP5Xo94wJXIzAyOtSb0QEIAUu7m8t6UZ9krbz0vAJqr92JpcK/lEXg91q6B9pEGqrykkd2EQplnifDSBw==}
+    engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
+    dependencies:
+      bl: 5.1.0
+      chalk: 5.1.2
+      cli-cursor: 4.0.0
+      cli-spinners: 2.6.1
+      is-interactive: 2.0.0
+      is-unicode-supported: 1.3.0
+      log-symbols: 5.1.0
+      strip-ansi: 7.0.1
+      wcwidth: 1.0.1
+    dev: false
+
   /ordered-binary/1.4.0:
     resolution: {integrity: sha512-EHQ/jk4/a9hLupIKxTfUsQRej1Yd/0QLQs3vGvIqg5ZtCYSzNhkzHoZc7Zf4e4kUlDaC3Uw8Q/1opOLNN2OKRQ==}
     dev: true
@@ -11973,7 +12005,6 @@ packages:
     resolution: {integrity: sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA==}
     dependencies:
       isarray: 0.0.1
-    dev: false
 
   /path-to-regexp/6.2.1:
     resolution: {integrity: sha512-JLyh7xT1kizaEvcaXOQwOc2/Yhw6KZOvPf1S8401UyLk86CU79LN3vl7ztXGm/pZ+YjoyAJ4rxmHwbkBXJX+yw==}
@@ -12282,7 +12313,7 @@ packages:
     resolution: {integrity: sha512-OovjwIzs9Te46vlEx7+uXB0PLijpwjXGKXjVGGPIGubGpq7uh5Xgf6D6FiJ/SzJMBosHDp6a2hiXOS97iBXcaA==}
     engines: {node: ^10 || ^12 || >=14}
     dependencies:
-      nanoid: 3.3.1
+      nanoid: 3.3.4
       picocolors: 1.0.0
       source-map-js: 1.0.2
     dev: true
@@ -12482,11 +12513,6 @@ packages:
       react-is: 16.13.1
     dev: true
 
-  /propagate/2.0.1:
-    resolution: {integrity: sha512-vGrhOavPSTz4QVNuBNdcNXePNdNMaO1xj9yBeH1ScQPjk/rhg9sSlCXPhMkFuaNNW/syTvYqsnbIJxMBfRbbag==}
-    engines: {node: '>= 8'}
-    dev: true
-
   /property-information/5.6.0:
     resolution: {integrity: sha512-YUHSPk+A30YPv+0Qf8i9Mbfe/C0hdPXk1s1jPVToV8pk8BQtpw10ct89Eo7OWkutrwqvT0eicAxlOg3dOAu8JA==}
     dependencies:
@@ -13283,6 +13309,14 @@ packages:
       onetime: 5.1.2
       signal-exit: 3.0.7
 
+  /restore-cursor/4.0.0:
+    resolution: {integrity: sha512-I9fPXU9geO9bHOt9pHHOhOkYerIMsmVaWB0rA2AI9ERh/+x/i7MV5HKBNrg+ljO5eoPVgCcnFuRjJ9uH6I/3eg==}
+    engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
+    dependencies:
+      onetime: 5.1.2
+      signal-exit: 3.0.7
+    dev: false
+
   /retry/0.13.1:
     resolution: {integrity: sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==}
     engines: {node: '>= 4'}
@@ -13512,6 +13546,17 @@ packages:
       semver: 7.0.0
     dev: true
 
+  /sinon/15.0.0:
+    resolution: {integrity: sha512-pV97G1GbslaSJoSdy2F2z8uh5F+uPGp3ddOzA4JsBOUBLEQRz2OAqlKGRFTSh2KiqUCmHkzyAeu7R4x1Hx0wwg==}
+    dependencies:
+      '@sinonjs/commons': 2.0.0
+      '@sinonjs/fake-timers': 9.1.2
+      '@sinonjs/samsam': 7.0.1
+      diff: 5.1.0
+      nise: 5.1.3
+      supports-color: 7.2.0
+    dev: true
+
   /sisteransi/1.0.5:
     resolution: {integrity: sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==}
     dev: true
@@ -13962,7 +14007,6 @@ packages:
     engines: {node: '>=12'}
     dependencies:
       ansi-regex: 6.0.1
-    dev: true
 
   /strip-bom/3.0.0:
     resolution: {integrity: sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==}
@@ -14494,74 +14538,6 @@ packages:
       yargs-parser: 21.1.1
     dev: true
 
-  /ts-jest/29.0.3_lr7fqxhx6o7ex6ma5v5npbw6ae:
-    resolution: {integrity: sha512-Ibygvmuyq1qp/z3yTh9QTwVVAbFdDy/+4BtIQR2sp6baF2SJU/8CKK/hhnGIDY2L90Az2jIqTwZPnN2p+BweiQ==}
-    engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
-    hasBin: true
-    peerDependencies:
-      '@babel/core': '>=7.0.0-beta.0 <8'
-      '@jest/types': ^29.0.0
-      babel-jest: ^29.0.0
-      esbuild: '*'
-      jest: ^29.0.0 || ^29.1.2
-      typescript: '>=4.3'
-    peerDependenciesMeta:
-      '@babel/core':
-        optional: true
-      '@jest/types':
-        optional: true
-      babel-jest:
-        optional: true
-      esbuild:
-        optional: true
-    dependencies:
-      '@jest/types': 29.1.2
-      bs-logger: 0.2.6
-      fast-json-stable-stringify: 2.1.0
-      jest: 29.1.2_@types+node@16.11.12
-      jest-util: 29.2.1
-      json5: 2.2.1
-      lodash.memoize: 4.1.2
-      make-error: 1.3.6
-      semver: 7.3.8
-      typescript: 4.9.3
-      yargs-parser: 21.1.1
-    dev: true
-
-  /ts-jest/29.0.3_o3wtcjdhyxuv43bggxcaucanwu:
-    resolution: {integrity: sha512-Ibygvmuyq1qp/z3yTh9QTwVVAbFdDy/+4BtIQR2sp6baF2SJU/8CKK/hhnGIDY2L90Az2jIqTwZPnN2p+BweiQ==}
-    engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
-    hasBin: true
-    peerDependencies:
-      '@babel/core': '>=7.0.0-beta.0 <8'
-      '@jest/types': ^29.0.0
-      babel-jest: ^29.0.0
-      esbuild: '*'
-      jest: ^29.0.0 || ^29.1.2
-      typescript: '>=4.3'
-    peerDependenciesMeta:
-      '@babel/core':
-        optional: true
-      '@jest/types':
-        optional: true
-      babel-jest:
-        optional: true
-      esbuild:
-        optional: true
-    dependencies:
-      '@jest/types': 29.1.2
-      bs-logger: 0.2.6
-      fast-json-stable-stringify: 2.1.0
-      jest: 29.3.1_k5ytkvaprncdyzidqqws5bqksq
-      jest-util: 29.2.1
-      json5: 2.2.1
-      lodash.memoize: 4.1.2
-      make-error: 1.3.6
-      semver: 7.3.8
-      typescript: 4.7.4
-      yargs-parser: 21.1.1
-    dev: true
-
   /ts-node/10.7.0_bjctuninx3nzqxltyvshqte2ni:
     resolution: {integrity: sha512-TbIGS4xgJoX2i3do417KSaep1uRAW/Lu+WAL2doDHC0D6ummjirVOXU5/7aiZotbQ5p1Zp9tP7U6cYhA0O7M8A==}
     hasBin: true
@@ -14651,6 +14627,7 @@ packages:
   /tsscmp/1.0.6:
     resolution: {integrity: sha512-LxhtAkPDTkVCMQjt2h6eBVY28KCjikZqZfMcC15YBeNjkgUpdCfBu5HoiOTDu86v6smE8yOjyEktJ8hlbANHQA==}
     engines: {node: '>=0.6.x'}
+    dev: false
 
   /tsutils/3.21.0_typescript@4.7.4:
     resolution: {integrity: sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==}
@@ -15041,6 +15018,7 @@ packages:
   /vary/1.1.2:
     resolution: {integrity: sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=}
     engines: {node: '>= 0.8'}
+    dev: false
 
   /vfile-location/3.2.0:
     resolution: {integrity: sha512-aLEIZKv/oxuCDZ8lkJGhuhztf/BW4M+iHdCwglA/eWc+vtuRFJj8EtgceYFX4LRjOhCAAiNHsKGssC6onJ+jbA==}
@@ -15403,6 +15381,7 @@ packages:
   /ylru/1.2.1:
     resolution: {integrity: sha512-faQrqNMzcPCHGVC2aaOINk13K+aaBDUPjGWl0teOXywElLjyVAB6Oe2jj62jHYtwsU49jXhScYbvPENK+6zAvQ==}
     engines: {node: '>= 4.0.0'}
+    dev: false
 
   /yn/3.1.1:
     resolution: {integrity: sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==}