Strapi 5 + typescript + jest === broken
Bug report
Required System information
- Node.js version: v20.16.0
- NPM version: 10.8.1 (yarn 1.22.22)
- Strapi version: 5.0.0-rc.8
- Database: sqlite
- Operating system: macOS Sonoma 14.5
- Is your project Javascript or Typescript: TypeScript
❯ yarn strapi report
yarn run v1.22.22
$ strapi report
Launched In: 141 ms
Environment: development
OS: darwin-arm64
Strapi Version: 5.0.0-rc.8
Node/Yarn Version: yarn/1.22.22 npm/? node/v20.16.0 darwin arm64
Edition: Community
Database: sqlite
[2024-08-04 18:40:08.311] info: Shutting down Strapi
[2024-08-04 18:40:08.312] info: Strapi has been shut down
✨ Done in 1.69s.
Describe the bug
Jest tests fail to run because Strapi won't load .ts config files.
Steps to reproduce the behavior
I published an example app that shows the issue to save you time, but I took the following steps to create the app:
- I set up a new Strapi project using:
npx create-strapi-app@rc --skip-cloud --typescript --use-yarn --dbclient sqlite strapi-typescript-test-bug-repro
- I then added Jest and its support libraries:
yarn add --dev jest ts-jest @types/jest
- I initialized the Jest config using:
yarn ts-jest config:init
-
I then added the
testPathIgnorePatternsto the Jest config as shown in the Strapi jest setup guide. -
I added a
strapi.tshelper file as explained in the Strapi jest setup guide. I converted it to TypeScript and used the newcreateStrapifunction as well. -
I added a
config/env/test/database.tsand aconfig/env/production/database.tsfile. -
I added the following lines to my
.envfile:
DATABASE_FILENAME=.tmp/data.db
TEST_DATABASE_FILENAME=.tmp/test_data.db
-
I added
tests/app.test.tsas described in the Strapi jest setup guide. This file has been converted to TypeScript as well. -
Run tests with
yarn test.
Expand this to see the test output...
❯ yarn test
yarn run v1.22.22
$ jest --forceExit --detectOpenHandles
console.warn
Config file not loaded, extension must be one of .js,.json): admin.ts
6 | export const setupStrapi = async () => {
7 | if (!instance) {
> 8 | await createStrapi().load();
| ^
9 | instance = strapi;
10 |
11 | instance.server.mount();
at logWarning (node_modules/@strapi/core/src/configuration/config-loader.ts:57:11)
at node_modules/@strapi/core/src/configuration/config-loader.ts:76:7
at Array.reduce (<anonymous>)
at loadConfigDir (node_modules/@strapi/core/src/configuration/config-loader.ts:65:32)
at Module.loadConfigDir [as loadConfiguration] (node_modules/@strapi/core/src/configuration/index.ts:74:38)
at new loadConfiguration (node_modules/@strapi/core/src/Strapi.ts:47:28)
at createStrapi (node_modules/@strapi/core/src/index.ts:11:18)
at setupStrapi (tests/helpers/strapi.ts:8:23)
at Object.<anonymous> (tests/app.test.ts:4:20)
console.warn
Config file not loaded, extension must be one of .js,.json): api.ts
6 | export const setupStrapi = async () => {
7 | if (!instance) {
> 8 | await createStrapi().load();
| ^
9 | instance = strapi;
10 |
11 | instance.server.mount();
at logWarning (node_modules/@strapi/core/src/configuration/config-loader.ts:57:11)
at node_modules/@strapi/core/src/configuration/config-loader.ts:76:7
at Array.reduce (<anonymous>)
at loadConfigDir (node_modules/@strapi/core/src/configuration/config-loader.ts:65:32)
at Module.loadConfigDir [as loadConfiguration] (node_modules/@strapi/core/src/configuration/index.ts:74:38)
at new loadConfiguration (node_modules/@strapi/core/src/Strapi.ts:47:28)
at createStrapi (node_modules/@strapi/core/src/index.ts:11:18)
at setupStrapi (tests/helpers/strapi.ts:8:23)
at Object.<anonymous> (tests/app.test.ts:4:20)
console.warn
Config file not loaded, extension must be one of .js,.json): database.ts
6 | export const setupStrapi = async () => {
7 | if (!instance) {
> 8 | await createStrapi().load();
| ^
9 | instance = strapi;
10 |
11 | instance.server.mount();
at logWarning (node_modules/@strapi/core/src/configuration/config-loader.ts:57:11)
at node_modules/@strapi/core/src/configuration/config-loader.ts:76:7
at Array.reduce (<anonymous>)
at loadConfigDir (node_modules/@strapi/core/src/configuration/config-loader.ts:65:32)
at Module.loadConfigDir [as loadConfiguration] (node_modules/@strapi/core/src/configuration/index.ts:74:38)
at new loadConfiguration (node_modules/@strapi/core/src/Strapi.ts:47:28)
at createStrapi (node_modules/@strapi/core/src/index.ts:11:18)
at setupStrapi (tests/helpers/strapi.ts:8:23)
at Object.<anonymous> (tests/app.test.ts:4:20)
console.warn
Config file not loaded, extension must be one of .js,.json): middlewares.ts
6 | export const setupStrapi = async () => {
7 | if (!instance) {
> 8 | await createStrapi().load();
| ^
9 | instance = strapi;
10 |
11 | instance.server.mount();
at logWarning (node_modules/@strapi/core/src/configuration/config-loader.ts:57:11)
at node_modules/@strapi/core/src/configuration/config-loader.ts:76:7
at Array.reduce (<anonymous>)
at loadConfigDir (node_modules/@strapi/core/src/configuration/config-loader.ts:65:32)
at Module.loadConfigDir [as loadConfiguration] (node_modules/@strapi/core/src/configuration/index.ts:74:38)
at new loadConfiguration (node_modules/@strapi/core/src/Strapi.ts:47:28)
at createStrapi (node_modules/@strapi/core/src/index.ts:11:18)
at setupStrapi (tests/helpers/strapi.ts:8:23)
at Object.<anonymous> (tests/app.test.ts:4:20)
console.warn
Config file not loaded, extension must be one of .js,.json): plugins.ts
6 | export const setupStrapi = async () => {
7 | if (!instance) {
> 8 | await createStrapi().load();
| ^
9 | instance = strapi;
10 |
11 | instance.server.mount();
at logWarning (node_modules/@strapi/core/src/configuration/config-loader.ts:57:11)
at node_modules/@strapi/core/src/configuration/config-loader.ts:76:7
at Array.reduce (<anonymous>)
at loadConfigDir (node_modules/@strapi/core/src/configuration/config-loader.ts:65:32)
at Module.loadConfigDir [as loadConfiguration] (node_modules/@strapi/core/src/configuration/index.ts:74:38)
at new loadConfiguration (node_modules/@strapi/core/src/Strapi.ts:47:28)
at createStrapi (node_modules/@strapi/core/src/index.ts:11:18)
at setupStrapi (tests/helpers/strapi.ts:8:23)
at Object.<anonymous> (tests/app.test.ts:4:20)
console.warn
Config file not loaded, extension must be one of .js,.json): server.ts
6 | export const setupStrapi = async () => {
7 | if (!instance) {
> 8 | await createStrapi().load();
| ^
9 | instance = strapi;
10 |
11 | instance.server.mount();
at logWarning (node_modules/@strapi/core/src/configuration/config-loader.ts:57:11)
at node_modules/@strapi/core/src/configuration/config-loader.ts:76:7
at Array.reduce (<anonymous>)
at loadConfigDir (node_modules/@strapi/core/src/configuration/config-loader.ts:65:32)
at Module.loadConfigDir [as loadConfiguration] (node_modules/@strapi/core/src/configuration/index.ts:74:38)
at new loadConfiguration (node_modules/@strapi/core/src/Strapi.ts:47:28)
at createStrapi (node_modules/@strapi/core/src/index.ts:11:18)
at setupStrapi (tests/helpers/strapi.ts:8:23)
at Object.<anonymous> (tests/app.test.ts:4:20)
console.warn
Config file not loaded, extension must be one of .js,.json): database.ts
6 | export const setupStrapi = async () => {
7 | if (!instance) {
> 8 | await createStrapi().load();
| ^
9 | instance = strapi;
10 |
11 | instance.server.mount();
at logWarning (node_modules/@strapi/core/src/configuration/config-loader.ts:57:11)
at node_modules/@strapi/core/src/configuration/config-loader.ts:76:7
at Array.reduce (<anonymous>)
at loadConfigDir (node_modules/@strapi/core/src/configuration/config-loader.ts:65:32)
at Module.loadConfigDir [as loadConfiguration] (node_modules/@strapi/core/src/configuration/index.ts:77:21)
at new loadConfiguration (node_modules/@strapi/core/src/Strapi.ts:47:28)
at createStrapi (node_modules/@strapi/core/src/index.ts:11:18)
at setupStrapi (tests/helpers/strapi.ts:8:23)
at Object.<anonymous> (tests/app.test.ts:4:20)
FAIL tests/app.test.ts
● strapi is defined
TypeError: Cannot destructure property 'client' of 'db.config.connection' as it is undefined.
6 | export const setupStrapi = async () => {
7 | if (!instance) {
> 8 | await createStrapi().load();
| ^
9 | instance = strapi;
10 |
11 | instance.server.mount();
at getDialect (node_modules/@strapi/database/src/dialects/index.ts:40:11)
at new Database (node_modules/@strapi/database/src/index.ts:80:20)
at node_modules/@strapi/core/src/Strapi.ts:275:11
at Strapi.get (node_modules/@strapi/core/src/container.ts:27:35)
at Strapi.get db [as db] (node_modules/@strapi/core/src/Strapi.ts:77:17)
at Object.register (node_modules/@strapi/core/src/providers/registries.ts:38:12)
at Strapi.register (node_modules/@strapi/core/src/Strapi.ts:400:13)
at Strapi.load (node_modules/@strapi/core/src/Strapi.ts:387:5)
at setupStrapi (tests/helpers/strapi.ts:8:5)
at Object.<anonymous> (tests/app.test.ts:4:3)
● Test suite failed to run
TypeError: Cannot destructure property 'client' of 'db.config.connection' as it is undefined.
23 |
24 | // close the connection to the database before deletion
> 25 | await strapi.db.connection.destroy();
| ^
26 |
27 | //delete test database after all tests have completed
28 | if (dbSettings?.connection?.filename) {
at getDialect (node_modules/@strapi/database/src/dialects/index.ts:40:11)
at new Database (node_modules/@strapi/database/src/index.ts:80:20)
at node_modules/@strapi/core/src/Strapi.ts:275:11
at Strapi.get (node_modules/@strapi/core/src/container.ts:27:35)
at Strapi.get db [as db] (node_modules/@strapi/core/src/Strapi.ts:77:17)
at cleanupStrapi (tests/helpers/strapi.ts:25:16)
at Object.<anonymous> (tests/app.test.ts:8:22)
Test Suites: 1 failed, 1 total
Tests: 1 failed, 1 total
Snapshots: 0 total
Time: 1.697 s, estimated 2 s
Ran all test suites.
error Command failed with exit code 1.
info Visit https://yarnpkg.com/en/docs/cli/run for documentation about this command.
As you can see, there are a lot of warnings like Config file not loaded, extension must be one of .js,.json): database.ts and then the tests fails because something internal to Strapi can't destructure config values.
Interestingly, if I pass the value { distDir: "dist" } to createStrapi in strapi.ts, and run yarn build before I run yarn test, the tests pass. Presumably this is because it's running the tests from the dist dir and yarn build is transpiling those files to .js files.
Expected behavior
I would expect there to be some way to run tests under TypeScript with Strapi that didn't require a build first. This is also broken under Strapi 4 in the same way and I was trying out Strapi 5 to see if I could make it work.
Screenshots
N/A
Code snippets
N/A
Additional context
I'm really happy Strapi is getting better TypeScript support! I'd love if it could get better testing support as well. Thanks so much for the great software!!
I'm not sure this is a documentation issue, @derrickmehaffy. Strapi is specifically dynamically loading .js and .json configuration files at runtime to configure the server: https://github.com/strapi/strapi/blob/v5.0.0-rc.10/packages/core/core/src/configuration/config-loader.ts#L5 -- I believe it would need to be a core framework change to also allow loading .ts files.
Unless, I guess, the docs say to use something else, like vitest or something like it that can incrementally compile and run tests.
This issue has been mentioned on Strapi Community Forum. There might be relevant details there:
https://forum.strapi.io/t/how-can-i-add-vitest-to-a-strapi-5-app/41255/1
running
I can make it it run by add jest.config.ts and ts-jest, but I still get this error message: TypeError: Cannot destructure property 'client' of 'db.config.connection' as it is undefined
const {defaults: tsjPreset} = require('ts-jest/presets');
const {pathsToModuleNameMapper} = require('ts-jest/dist');
const {compilerOptions} = require('./tsconfig.test.json');
export default async () => {
return {
roots: ["<rootDir>/src/", "<rootDir>/config/", "<rootDir>/tests/"],
testMatch: [
"**/__tests__/**/*.+(ts|tsx|js)",
"**/?(*.)+(spec|test).+(ts|tsx|js)"
],
testEnvironment: 'node',
transform: {
...tsjPreset.transform
},
testPathIgnorePatterns: [
"<rootDir>/__mocks__/*",
"node_modules",
"\\.tmp",
"\\.cache",
"<rootDir>.*/public",
"<rootDir>/dist/*",
],
moduleNameMapper: {
...pathsToModuleNameMapper(compilerOptions.paths, {prefix: '<rootDir>/'})
},
setupFilesAfterEnv: [
"<rootDir>/tests/helpers/strapi.ts" // Path to your setup file
],
testTimeout: 60000,
verbose: false,
};
};
Hello,
Currently, if you follow the doc, the main problem I encountered was the database with /env/test/database.ts seems not to work.
I'm able to fix this problem, you need to configure in your config/database, your database for testing (ex. sqlite file) and define differents modes like this :
import path from "path";
export default ({ env }) => {
let client = env("DATABASE_CLIENT", "postgres");
if (process.env.NODE_ENV === "test") {
client = "sqlite";
}
const connections = {
postgres: {
connection: {
connectionString: env("DATABASE_URL"),
host: env("DATABASE_HOST", "localhost"),
port: env.int("DATABASE_PORT", 5432),
database: env("DATABASE_NAME", "strapi"),
user: env("DATABASE_USERNAME", "strapi"),
password: env("DATABASE_PASSWORD", "strapi"),
ssl: env.bool("DATABASE_SSL", false) && {
key: env("DATABASE_SSL_KEY", undefined),
cert: env("DATABASE_SSL_CERT", undefined),
ca: env("DATABASE_SSL_CA", undefined),
capath: env("DATABASE_SSL_CAPATH", undefined),
cipher: env("DATABASE_SSL_CIPHER", undefined),
rejectUnauthorized: env.bool(
"DATABASE_SSL_REJECT_UNAUTHORIZED",
true
),
},
schema: env("DATABASE_SCHEMA", "public"),
},
pool: {
min: env.int("DATABASE_POOL_MIN", 2),
max: env.int("DATABASE_POOL_MAX", 10),
},
},
sqlite: {
connection: {
filename: path.join(
__dirname,
"..",
"..",
env("DATABASE_FILENAME", ".tmp/data.db")
),
},
useNullAsDefault: true,
debug: false,
},
};
return {
connection: {
client,
...connections[client],
acquireConnectionTimeout: env.int("DATABASE_CONNECTION_TIMEOUT", 60000),
},
};
}
My test suite has passed successfully, but I have come across a new bug :