diff --git a/package.json b/package.json
index 9c3c39bf9360454a148e3a149b7115f7c654c907..850a52ceb605c59dd9c0b1990552d4348262625e 100644
--- a/package.json
+++ b/package.json
@@ -22,7 +22,7 @@
 		"cy:open": "cypress open",
 		"cy:run": "cypress run",
 		"e2e": "start-server-and-test start:test http://localhost:61812 cy:run",
-		"mocha": "cd packages/backend && cross-env TS_NODE_FILES=true TS_NODE_TRANSPILE_ONLY=true TS_NODE_PROJECT=\"./test/tsconfig.json\" npx mocha",
+		"mocha": "cd packages/backend && cross-env NODE_ENV=test TS_NODE_FILES=true TS_NODE_TRANSPILE_ONLY=true TS_NODE_PROJECT=\"./test/tsconfig.json\" npx mocha",
 		"test": "npm run mocha",
 		"format": "gulp format",
 		"clean": "node ./scripts/clean.js",
diff --git a/packages/backend/package.json b/packages/backend/package.json
index c06f48f546dd17da5bed7a568f749bcd8b3e9634..6467588956452dbcb86c95864004584b87ce315b 100644
--- a/packages/backend/package.json
+++ b/packages/backend/package.json
@@ -6,7 +6,7 @@
 		"build": "tsc -p tsconfig.json || echo done. && tsc-alias -p tsconfig.json",
 		"watch": "node watch.mjs",
 		"lint": "eslint --quiet \"src/**/*.ts\"",
-		"mocha": "cross-env TS_NODE_FILES=true TS_NODE_TRANSPILE_ONLY=true TS_NODE_PROJECT=\"./test/tsconfig.json\" mocha",
+		"mocha": "cross-env NODE_ENV=test TS_NODE_FILES=true TS_NODE_TRANSPILE_ONLY=true TS_NODE_PROJECT=\"./test/tsconfig.json\" mocha",
 		"test": "npm run mocha"
 	},
 	"resolutions": {
diff --git a/packages/backend/test/loader.js b/packages/backend/test/loader.js
index 016f32f1a8b9a3147d69f362136e10e7cef98d81..6b21587e32e4786b4162816fea5b0a5e989377c6 100644
--- a/packages/backend/test/loader.js
+++ b/packages/backend/test/loader.js
@@ -1,37 +1,34 @@
-import path from 'path'
-import typescript from 'typescript'
-import { createMatchPath } from 'tsconfig-paths'
-import { resolve as BaseResolve, getFormat, transformSource } from 'ts-node/esm'
+/**
+ * ts-node/esmローダーに投げる前にpath mappingを解決する
+ * 参考
+ * - https://github.com/TypeStrong/ts-node/discussions/1450#discussioncomment-1806115
+ * - https://nodejs.org/api/esm.html#loaders
+ * ※ https://github.com/TypeStrong/ts-node/pull/1585 が取り込まれたらこのカスタムローダーは必要なくなる
+ */
 
-const { readConfigFile, parseJsonConfigFileContent, sys } = typescript
+import { resolve as resolveTs, load } from 'ts-node/esm';
+import { loadConfig, createMatchPath } from 'tsconfig-paths';
+import { pathToFileURL } from 'url';
 
-const __dirname = path.dirname(new URL(import.meta.url).pathname)
+const tsconfig = loadConfig();
+const matchPath = createMatchPath(tsconfig.absoluteBaseUrl, tsconfig.paths);
 
-const configFile = readConfigFile('./test/tsconfig.json', sys.readFile)
-if (typeof configFile.error !== 'undefined') {
-  throw new Error(`Failed to load tsconfig: ${configFile.error}`)
+export function resolve(specifier, ctx, defaultResolve) {
+	let resolvedSpecifier;
+	if (specifier.endsWith('.js')) {
+		// maybe transpiled
+		const specifierWithoutExtension = specifier.substring(0, specifier.length - '.js'.length);
+		const matchedSpecifier = matchPath(specifierWithoutExtension);
+		if (matchedSpecifier) {
+			resolvedSpecifier = pathToFileURL(`${matchedSpecifier}.js`).href;
+		}
+	} else {
+		const matchedSpecifier = matchPath(specifier);
+		if (matchedSpecifier) {
+			resolvedSpecifier = pathToFileURL(matchedSpecifier).href;
+		}
+	}
+	return resolveTs(resolvedSpecifier ?? specifier, ctx, defaultResolve);
 }
 
-const { options } = parseJsonConfigFileContent(
-  configFile.config,
-  {
-    fileExists: sys.fileExists,
-    readFile: sys.readFile,
-    readDirectory: sys.readDirectory,
-    useCaseSensitiveFileNames: true,
-  },
-  __dirname
-)
-
-export { getFormat, transformSource }  // こいつらはそのまま使ってほしいので re-export する
-
-const matchPath = createMatchPath(options.baseUrl, options.paths)
-
-export async function resolve(specifier, context, defaultResolve) {
-  const matchedSpecifier = matchPath(specifier.replace('.js', '.ts'))
-  return BaseResolve(  // ts-node/esm の resolve に tsconfig-paths で解決したパスを渡す
-    matchedSpecifier ? `${matchedSpecifier}.ts` : specifier,
-    context,
-    defaultResolve
-  )
-}
+export { load };