Testing svelte components with import.meta.env

Viewed 3602

I'm struggeling now for a couple of days to get my testsetup running. Rough outline: Vite, Svelte (with ts), Jest.

I'm using import.meta.env.SOMENAME for my environment vars although this works fine for development as soon as a component uses import.meta.env the test will fail with:

SyntaxError: Cannot use 'import.meta' outside a module

I've tried different transformers, babel-plugins and configs but never succeeded...

My jest config:

"jest": {
    "globals": {
      "ts-jest": {
        "isolatedModules": true
      }
    },
    "verbose": true,
    "transform": {
      "^.+\\.svelte$": [
        "svelte-jester",
        {
          "preprocess": true
        }
      ],
      "^.+\\.ts$": "ts-jest",
      "^.+\\.js$": "babel-jest"
    },
    "setupFilesAfterEnv": ["<rootDir>/setupTests.ts"],
    "moduleFileExtensions": ["js", "ts", "svelte"]
  }

babel.config.js

module.exports = {
  presets: [
    [
      "@babel/preset-env",
      {
        targets: {
          node: "current"
        }
      }
    ]
  ]
};

svelte.config.cjs

const sveltePreprocess = require('svelte-preprocess')

module.exports = {
  emitCss: true,
  preprocess: sveltePreprocess()
};

Among other things I tried to use @babel/plugin-syntax-import-meta but ended up with the same error. Also vite-jest looked very promising but again I couldn't make it work.

I appreciate every hint I can get. If I can provide any additional info please let me know. Also my knowledge of vite and babel is very limited so REALLY appreciate any help IU can get on this topic.

Update (Solution)

So If you use babel you could use babel-preset-vite. The approach with esbuild-jest from Apu is also good solution that many people use. Unfortunately those things didn't work for me so I decided to use a workaround with vite's define.

This workaround consists of two steps.

  1. replace import.meta.env with process.env (if this is a deal breaker for you then I hope you have luck with the solutions above) You only have to replace the instances in files you want to test with jest.
  2. Update Vite config with define. This step is necessary or your build will break (dev will still work)

vite.config.js

const dotEnvConfig = dotenv.config();

export default defineConfig({
  define: {
    "process.env.NODE_ENV": `"${process.env.NODE_ENV}"`,
    "process.env.VITE_APP_SOMENAME": `"${process.env.VITE_APP_SOMENAME}"`
  },
  ...
)};

I know this is just a workaround but maybe this helps someone. Thanks & Good Luck.

2 Answers

A more recent alternative to Jest that understands import.meta.env is Vitest.

It should require almost no additional configuration to get started and it's highly compatible with Jest so it requires few changes to the actual tests.

The advantages of Vitest over Jest for this use case are:

  • It's designed specifically for Vite and will process tests on demand
  • It will reuse your existing Vite configuration:
    • Any define variables will be replaced as expected
    • Extensions that Vite adds to import.meta will be available as usual

I was having issues with svelte component testing as well using jest. babel is not good at resolving import.meta. I used esbuild-jest to transform both ts and js files. It solves the issue with the import.meta. Here is my jest.config.cjs.

npm i esbuild esbuild-jest -D

const { pathsToModuleNameMapper } = require('ts-jest/utils');
const { compilerOptions } = require('./tsconfig');

const config = {
  "transform": {
    "^.+\\.svelte$": [
      "svelte-jester",
      {
        "preprocess": true
      }
    ],
    "^.+\\.(ts|tsx|js|jsx)$":  ["esbuild-jest"]
  },
  "moduleFileExtensions": [
    "js",
    "ts",
    "tsx",
    "svelte"
  ],
  "setupFilesAfterEnv": [
    "@testing-library/jest-dom/extend-expect"
  ],
  "collectCoverageFrom": [
    "**/*.(t|j)s",
    "**/*.svelte"
  ],
  coverageProvider: 'v8',
  "coverageDirectory": "./coverage",
  "coveragePathIgnorePatterns": [
    "/node_modules/",
    "/.svelte-kit/"
  ],
  "moduleNameMapper": pathsToModuleNameMapper(compilerOptions.paths, {prefix: '<rootDir>/'})
};
  
  
module.exports = config;
Related