strapi / documentation

Strapi Documentation
https://docs.strapi.io
Other
1.01k stars 1.08k forks source link

Strapi 5 + typescript + jest === broken #2179

Open corasaurus-hex opened 1 month ago

corasaurus-hex commented 1 month ago

Bug report

Required System information

❯ 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:

  1. 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
  1. I then added Jest and its support libraries:
yarn add --dev jest ts-jest @types/jest
  1. I initialized the Jest config using:
yarn ts-jest config:init
  1. I then added the testPathIgnorePatterns to the Jest config as shown in the Strapi jest setup guide.

  2. I added a strapi.ts helper file as explained in the Strapi jest setup guide. I converted it to TypeScript and used the new createStrapi function as well.

  3. I added a config/env/test/database.ts and a config/env/production/database.ts file.

  4. I added the following lines to my .env file:

DATABASE_FILENAME=.tmp/data.db
TEST_DATABASE_FILENAME=.tmp/test_data.db
  1. I added tests/app.test.ts as described in the Strapi jest setup guide. This file has been converted to TypeScript as well.

  2. 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 () 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. (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 () 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. (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 () 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. (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 () 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. (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 () 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. (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 () 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. (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 () 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. (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. (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. (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!!

corasaurus-hex commented 3 weeks ago

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.

corasaurus-hex commented 3 weeks ago

Unless, I guess, the docs say to use something else, like vitest or something like it that can incrementally compile and run tests.

strapi-bot commented 3 weeks ago

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

saltict commented 1 week ago

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,
  };
};