Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
corasaurus-hex opened this issue Aug 5, 2024 · 5 comments
Open

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

corasaurus-hex opened this issue Aug 5, 2024 · 5 comments

Comments

@corasaurus-hex
Copy link

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:

  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 (<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!!

@derrickmehaffy derrickmehaffy transferred this issue from strapi/strapi Aug 15, 2024
@corasaurus-hex
Copy link
Author

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
Copy link
Author

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
Copy link

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
Copy link

saltict commented Sep 5, 2024

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

@Bastiendsp
Copy link

Bastiendsp commented Oct 18, 2024

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 :
{A6625EFF-C1A8-4F66-BC53-50B2807130AF}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants