I am trying to run Jest unit tests on function logic that is imported from a Svelte component. The program runs fine except when I try to import into Jest: I can console log the env variable in the line below ok.
The problem is that this line gives me an error when i try to run the unit test, I guess because its trying to import the file into the jest test. It runs fine when the program is actually running, but when the Jest test tries to import it, the context changes or ... something. Anyway, here's the line, from my file src/routes/signup/index.svelte:
// in the script tag of a svelte component
<script context="module">
const googleRecaptchaSiteKey =
typeof import.meta.env.VITE_GOOGLE_RECAPTCHA_KEY === "string"
? import.meta.env.VITE_GOOGLE_RECAPTCHA_KEY
: ""
export function foo() {
// ...
}
</script>
This code must run in my Jest test because it gives the error when I run npm test, so here's how I am executing that code:
import { foo } from "../src/routes/signup/index.svelte"
// It must execute the whole component when I import from it?
describe("signup page logic", () => {
test("ensure that the signup form button enablement conditions work properly", () => {
const failureOne = foo()
}
}
The error message itself:
/home/rlm/Code/projName/src/routes/signup/index.svelte:446
const googleRecaptchaSiteKey = typeof import.meta.env.VITE_GOOGLE_RECAPTCHA_KEY === "string"
^^^^
SyntaxError: Cannot use 'import.meta' outside a module
> 1 | import { updateEnabledSubmitSignup } from "../src/routes/signup/index.svelte"
Now since writing the above text, I have been adventuring for approx 27 minutes to discover a solution. What I have done is try to follow guides.
Per the instruction of Environment variables with SvelteKit I did:
in src/lib/variables.ts:
export const variables = {
foo: import.meta.env.VITE_FOO,
secondRecaptchaKey: import.meta.env.VITE_SECOND_RECAPTCHA_KEY,
}
And then I import it into the Svelte file: import { variables } from "../../lib/variables"
I run npm run dev and it console logs the value fine.
But then when I run npm test I get:
src/lib/variables.ts:3:23 - error TS1343: The 'import.meta' meta-property is only allowed when the '--module' option is 'es2020', 'es2022', 'esnext', 'system', 'node12', or 'nodenext'.
3 secondRecaptchaKey: import.meta.env.VITE_SECOND_RECAPTCHA_KEY,
TS1343: The 'import.meta' meta-property is only allowed when the '--module' option is 'es2020', 'esnext', or 'system'
Test suite failed to run import.meta.env.VITE_* does also but I tried to follow it and it fails even after installing vite-plugin-environment and babel-plugin-transform-import-meta and adding them to the babel plugins:
export const variables = { // logs with all values undefined
foo: process.env.VITE_FOO,
secondRecaptchaKey: process.env.VITE_SECOND_RECAPTCHA_KEY,
}
TS1343: The 'import.meta' meta-property is only allowed when the '--module' option is 'es2020', 'esnext', or 'system' also has advice that fails for me, or i have done it wrong. In my current state npm test logs the env variables as undefined.
edit: For anyone in the future who has this problem, I was able to go around the problem by doing this totally-good-enough workaround:
jest.mock("../src/lib/envVariables", () => ({
envVariables: { foo: "bar", secondRecaptchaKey: "someMockValue" },
}))
I credit nstanard in this post for saving us from the hassle
I need to import a file into my project when an environment variable is set, say dist/built.esm.js. It's implied that when the environment variable is set, the file will exist, otherwise it may or may not exist. It seems straightforward to just wrap a call to import in an if statement that checks for the environment variable, but Vue throws the below warning even if the if statement never passes:
And the code:
if (process.env.VUE_APP_USE_COMPILED == 'true') {
const compiledPackage = require('./dist/built.esm.js')
Vue.use(compiledPackage)
}
Setting the if statement to always be false in a nondeterminate way (setting a string var and then comparing it to a different value, instead of just if (false)) results in the same problem, which rules out any possibility of the environment variable being 'true' when it isn't supposed to be.
A temporary workaround I found is to wrap the import in a try/catch, which instead displays a warning instead of an error:
How can I get rid of the errors and warnings completely? I do want it to still error if the file doesn't exist but the environment variable has been set to true, but it shouldn't fail or warn on compilation if the statement hasn't even executed yet.
Does this work?
if (process.env.VUE_APP_USE_COMPILED == 'true') {
import('dist/built.esm.js')
.then(obj => Vue.use(obj))
.catch(err => console.log(err));
}
I managed to figure this out on my own. I used the resolve.alias property in the Webpack configuration to allow a 'soft fail' when the file doesn't exist. I changed my import to use an alias (my-compiled-package-alias), which would conditionally resolve to either the built file or an empty dummy file (dev/import-dummy.js). I had to use resolve.alias rather than resolve.fallback, since Vue2 uses Webpack v4 which doesn't include the latter property.
My updated code:
if (process.env.VUE_APP_USE_COMPILED == 'true') {
const compiledPackage = require('my-compiled-package-alias')
Vue.use(compiledPackage)
}
In my vue.config.js:
module.exports = {
...
configureWebpack: {
resolve: {
alias: {
"my-compiled-package-alias":
process.env.VUE_APP_USE_COMPILED ? "./dist/built.esm.js": "./import-dummy.js"
}
}
},
...
}
but it shouldn't fail or warn on compilation if the statement hasn't even executed yet
Compilation happens before execution. If you get a compile error, that means something went wrong before your code was executed, including any conditionals.
What I believe happens here is that you're using webpack, and it's trying to include dist/built.esm.js in your bundle. Behind the scenes, webpack actually replaces require with some magic. To get around this, use __non_webpack_require__ instead
You could try setting up a compile-time constant using DefinePlugin in your webpack config, maybe something like
plugins: [
new webpack.DefinePlugin({
// this is resolved compile-time
USE_COMPILED: process.env.VUE_APP_USE_COMPILED == 'true'
})
]
Then, in your code
if (USE_COMPILED) require('./dist/built.esm.js')
Here the value of USE_COMPILED should be replaced by webpack compile-time with true if your environment var is set to 'true', and false otherwise.
So, I'm working my way through learning Jest, and in a current Aurelia project, the internal working of the generated main.js script imports a configuration object (environment). Note this code is all as-generated.
// main.js
import environment from './environment';
import {PLATFORM} from 'aurelia-pal';
import 'babel-polyfill';
import * as Bluebird from 'bluebird';
// remove out if you don't want a Promise polyfill (remove also from webpack.config.js)
Bluebird.config({ warnings: { wForgottenReturn: false } });
export function configure(aurelia) {
aurelia.use
.standardConfiguration()
.feature(PLATFORM.moduleName('resources/index'));
// Uncomment the line below to enable animation.
// aurelia.use.plugin(PLATFORM.moduleName('aurelia-animator-css'));
// if the css animator is enabled, add swap-order="after" to all router-view elements
// Anyone wanting to use HTMLImports to load views, will need to install the following plugin.
// aurelia.use.plugin(PLATFORM.moduleName('aurelia-html-import-template-loader'));
if (environment.debug) {
aurelia.use.developmentLogging();
}
if (environment.testing) {
aurelia.use.plugin(PLATFORM.moduleName('aurelia-testing'));
}
return aurelia.start().then(() => aurelia.setRoot(PLATFORM.moduleName('app')));
}
The environment object is just holding a couple simple values:
export default {
debug: true,
testing: true
};
Now, when I want to test the branching logic in main.js, I want to be able to flip those booleans to ensure they do or don't execute the config changes as appropriate:
import {configure} from '../../src/main';
import environment from '../../src/environment';
/* later... */
describe('when the environment is not set to debug', () => {
environment.debug = false;
it('should not configure development logging', () => {
configure(aureliaMock);
expect(aureliaMock.use.developmentLogging.mock.calls.length).toBe(0);
});
});
This does not work, as the version of environment being checked inside the configure() function still has the values in the source module. I recognize that environment in this case is my local value, but what I don't know is how to affect the instance of environment that's being checked.
I tried using the jest.mock() syntax you'd use with an ES6 class constructor, but that doesn't work either. I will probably change the configure() signature to accept an environment for testing, but before doing so I wanted to see if there's a way to do this via mocks first.
I'd like to override some values at test-time, specifically setting my retries for an http service to 1 (immediate failure, no retries). Our project uses node-config. According to the docs I can override with NODE_CONFIG env variable:
node myapp.js --NODE_CONFIG='{"Customer":{"dbConfig":{"host":"customerdb.prod"}}}'
Well I would prefer to do this in my test, but not for all tests. The code says that you can allow config mutations by setting ALLOW_CONFIG_MUTATIONS.
process.env.ALLOW_CONFIG_MUTATIONS = "true";
const importFresh = require('import-fresh');
importFresh("config");
process.env.NODE_CONFIG = JSON.stringify({httpServices:{integration:{enrich: {retryInterval: 1, retries: 1}}}});
expect(process.env.NODE_CONFIG, 'NODE_CONFIG not set').to.exist();
expect(process.env.NODE_CONFIG, 'NODE_CONFIG not set').to.match(/retryInterval/);
expect(process.env.ALLOW_CONFIG_MUTATIONS, 'ALLOW_CONFIG_MUTATIONS not set').to.equal("true");
const testConfig = require("config");
console.dir(testConfig.get("httpServices.integration.enrich"));
expect(testConfig.get("httpServices.integration.enrich.retryInterval"), 'config value not set to 1').to.equal(1);
Result:
{ url: 'https://internal-**********',
retryInterval: 5000,
retries: 5 }
`Error: config value not set to 1: Expected 5000 to equal specified value: 1`
How do I get this override to work?
(expect is from Hapi.js Code library)
I'm one of the maintainers of node-config. Your bug is that you used require the second time when you should have used importFresh again.
Your first use of "importFresh()" does nothing different than require() would, because it is the first use of require().
After setting some variables, you call require(), which will return the copy of config already generated and cached, ignoring the effects of the environment variables set.
You only needed to use importFresh() once, where you currently use require(). This will cause a "fresh" copy of the config object to be returned, as you expected.
Simply changing config's property worked for me.
For example:
const config = require( 'config' );
config.httpServices.integration.enrich.retryInterval = 1;
// Do your tests...
UPD: Make sure that overrides are done before anyone calls the first config.get(), because the config object is made immutable as soon as any client uses the values via get().
Joining late, but other answers did not fit with the testing standard in my project, so here is what I came up with
TL;DR
Use mocks..
Detailed Answer
node-config uses a function get to get the configuration values.
By mocking the function get you can easily modify any configuration you see fit..
My personal favorite library is sinon
Here is an implementation of a mock with sinon
const config = require('config');
const sinon = require('sinon');
class MockConfig {
constructor () {
this.params = {};
this.sandbox = sinon.sandbox.create();
}
withConfValue (confKey, confValue) {
this.params.confValues[confKey] = confValue;
return this;
}
reset () {
this.params.confValues: {};
return this;
}
restore() {
this.sandbox.restore();
}
apply () {
this.restore(); // avoid duplicate wrapping
this.sandbox.stub(config, 'get').callsFake((configKey) => {
if (this.params.confValues.hasOwnProperty(configKey)) {
return this.params.confValues[configKey];
}
// not ideal.. however `wrappedMethod` approach did not work for me
// https://stackoverflow.com/a/57017971/1068746
return configKey
.split('.')
.reduce((result, item) => result[item], config)
});
}
}
const instance = new MockConfig();
MockConfig.instance = () => instance;
module.exports = MockConfig;
Usage would be
const mockConfig = require('./mock_config').instance();
...
beforeEach(function () {
mockConfig.reset().apply();
})
afterEach(function () {
mockConfig.reset().clear();
})
it('should do something') {
mockConfig.withConfValue('some_topic.some_field.property', someValue);
... rest of the test ...
}
Assumptions
The only assumption this approach makes is that you adhere to node-config way of reading the configuration (using the get function) and not bypass it by accessing fields directly.
It's better to create a development.json, production.json et test.json in your config folder node-config will use it your app configuration.
you just net to set your NODE_ENV to use the specific file.
Hope it helps :)
How do I read node environment variables in TypeScript?
If i use process.env.NODE_ENV I have this error :
Property 'NODE_ENV' does not exist on type 'ProcessEnv'
I have installed #types/node but it didn't help.
Once you have installed #types/node in your project, you can tell TypeScript exactly what variables are present in your process.env:
environment.d.ts
declare global {
namespace NodeJS {
interface ProcessEnv {
GITHUB_AUTH_TOKEN: string;
NODE_ENV: 'development' | 'production';
PORT?: string;
PWD: string;
}
}
}
// If this file has no import/export statements (i.e. is a script)
// convert it into a module by adding an empty export statement.
export {}
Usage:
process.env.GITHUB_AUTH_TOKEN; // $ExpectType string
This method will give you IntelliSense, and it also takes advantage of string literal types.
Note: the snippet above is module augmentation. Files containing module augmentation must be modules (as opposed to scripts). The difference between modules and scripts is that modules have at least one import/export statement.
In order to make TypeScript treat your file as a module, just add one import statement to it. It can be anything. Even export {} will do.
There's no guarantee of what (if any) environment variables are going to be available in a Node process - the NODE_ENV variable is just a convention that was popularised by Express, rather than something built in to Node itself. As such, it wouldn't really make sense for it to be included in the type definitions. Instead, they define process.env like this:
export interface ProcessEnv {
[key: string]: string | undefined
}
Which means that process.env can be indexed with a string in order to get a string back (or undefined, if the variable isn't set). To fix your error, you'll have to use the index syntax:
let env = process.env["NODE_ENV"];
Alternatively, as jcalz pointed out in the comments, if you're using TypeScript 2.2 or newer, you can access indexable types like the one defined above using the dot syntax - in which case, your code should just work as is.
just add before use process.env.NODE_ENV follow lines:
declare var process : {
env: {
NODE_ENV: string
}
}
You can use a Type Assertion for this
Sometimes you’ll end up in a situation where you’ll know more about a
value than TypeScript does. Usually this will happen when you know the
type of some entity could be more specific than its current type.
Type assertions are a way to tell the compiler “trust me, I know what
I’m doing.” A type assertion is like a type cast in other languages,
but performs no special checking or restructuring of data. It has no
runtime impact, and is used purely by the compiler. TypeScript assumes
that you, the programmer, have performed any special checks that you
need.
Example
const nodeEnv: string = (process.env.NODE_ENV as string);
console.log(nodeEnv);
Alternatively you might find a library such as env-var more suitable for this specific purpose --
"solution for loading and sanitizing environment variables in node.js with correct typings"
1. Create a .env file
# Contents of .env file
AUTHENTICATION_API_URL="http://localhost:4000/login"
GRAPHQL_API_URL="http://localhost:4000/graphql"
2. Load your .env file into process.env with dotenv
We can leverage dotenv to set environment-specific process.env variables. Create a file called config.ts in your src/ directory and populate as follows:
// Contents of src/config.ts
import {config as configDotenv} from 'dotenv'
import {resolve} from 'path'
switch(process.env.NODE_ENV) {
case "development":
console.log("Environment is 'development'")
configDotenv({
path: resolve(__dirname, "../.env.development")
})
break
case "test":
configDotenv({
path: resolve(__dirname, "../.env.test")
})
break
// Add 'staging' and 'production' cases here as well!
default:
throw new Error(`'NODE_ENV' ${process.env.NODE_ENV} is not handled!`)
}
Note: This file needs to get imported in your top-most file, likely your src/index.ts via import './config' (placed before all other imports)
3. Check ENV variables and define IProcessEnv
After combining a few methods above, we can add some runtime checks for sanity to guarantee that our declared IProcessEnv interface reflects what ENV variables are set in our .env.* files. The contents below can also live in src/config.ts
// More content in config.ts
const throwIfNot = function<T, K extends keyof T>(obj: Partial<T>, prop: K, msg?: string): T[K] {
if(obj[prop] === undefined || obj[prop] === null){
throw new Error(msg || `Environment is missing variable ${prop}`)
} else {
return obj[prop] as T[K]
}
}
// Validate that we have our expected ENV variables defined!
['AUTHENTICATION_API_URL', 'GRAPHQL_API_URL'].forEach(v => {
throwIfNot(process.env, v)
})
export interface IProcessEnv {
AUTHENTICATION_API_URL: string
GRAPHQL_API_URL: string
}
declare global {
namespace NodeJS {
interface ProcessEnv extends IProcessEnv { }
}
}
This will give us proper IntelliSense/tslint type checking, as well as some sanity when deploying to various environments.
Note that this also works for a ReactJS app (as opposed to a NodeJS server app). You can omit Step (2) because this is handled by create-react-app.
After executing with typescript latest version:
npm install --save #types/node
you can use process.env directly.
console.log(process.env["NODE_ENV"])
you will see the expected result if you have set NODE_ENV.
what worked for me is that everywhere I want to use process.env I first import dotenv and call config() on it. Also, remember to append ! at the end and ensure the attribute is defined in your .env file
import dotenv from 'dotenv';
dotenv.config();
export const YOUR_ATTRIBUTE = process.env.YOUR_ATTRIBUTE!;
Here is a short function which is guaranteed to pull the process.env value as a string -- or to throw an error otherwise.
For something more powerful (but also bigger), others here have suggested env-var.
/**
* Returns value stored in environment variable with the given `name`.
* Throws Error if no such variable or if variable undefined; thus ensuring type-safety.
* #param name - name of variable to fetch from this process's environment.
*/
export function env(name: string): string {
const value = process.env[name];
if (!value) {
throw new Error(`Missing: process.env['${name}'].`);
}
return value;
}
You should then be able to write code like:
let currentEnvironment: string;
currentEnvironment = env('NODE_ENV');
I know this will help someone who searches for this and can't find the simple answer to why your proccess.env variables are making your compiler whine:
Install #types/node:
npm i #types/node
Then where ever you're including your env as a string, do this:
process.env.YOUR_ENV ?? ''
The double question marks allow you to check for null/undefined.
Install #types/node by running npm i #types/node
Add "types": [ "node" ] to your tsconfig.json file in the compilerSection section.
here's my solution with envalid (validating and accessing environment variables in Node.js)
import { str, cleanEnv } from 'envalid'
const env = cleanEnv(process.env, {
clientId: str(),
clientSecret: str(),
})
// and now the env is validated and no longer undefined
const clientId = env.clientId
Just typecast the process.env.YOUR_VAR
Example:
mongoose
.connect(String(process.env.MONGO_URL), {
useNewUrlParser: true,
useFindAndModify: false
})
.then(() => console.log('DB connected'))
.catch((err: any) => console.error(err));
Complementing previous responses and after some time with this problem, even installing #types/node, I found this answer. In short, just run a reload window:
"...Although, you probably have to restart typescript language server if it still uses previous version of the tsconfig. In order to do this in VS Code, you do Ctrl+Shift+P and Reload Window or TypeScript: Restart TS server if available..."
The best and easiest way to use node process.env in your typescript project is to first compile with tsc then run the compiled javascript file with node supplying your ENV var. Example (first make sure tsconfig.ts is what you want for the output directory also the name of compiled file, I am using dist as output directory and index.js as example):
cd my-typescriptproject
tsc
NODE_ENV=test node ./dist/index.js
Important note: if you have a web app and you are using webpack.DefinePlugin to define process.env on your window, then these are they typings you are looking for:
declare namespace process {
let env: {
// this is optional, if you want to allow also
// other values than the ones listed below, they will have type
// string | undefined, which is the default
[key: string]: string
commit_hash: string
build_time: string
stage: string
version: string
// ... etc.
}
}
For anyone coming here looking for an answer for Create React App projects specifically, your variable names should start with REACT_APP_
Read more here: https://facebook.github.io/create-react-app/docs/adding-custom-environment-variables
create a file like global.d.ts
declare global {
namespace NodeJS {
interface ProcessEnv {
SECRET: string;
}
}
}
export {};
tutorial by Christian Höller
You could also use a type guard function. Something like this that has a return type of
parameterName is string
e.g.
function isEnvVarSpecified(envVar: string | undefined): envVar is string {
if(envVar === undefined || envVar === null) {
return false;
}
if(typeof envVar !== 'string'){
return false;
}
return true;
}
You can then call this as a type guard:
function myFunc() {
if(!isEnvVarSpecified(process.env.SOME_ENV_VAR')){
throw new Error('process.env.SOME_ENV_VAR not found')
}
// From this point on the ts compiler won't complain about
// process.env.SOME_ENV_VAR being potentially undefined
}
I wrote a module to simplify this. It has no dependencies so it's reasonably lightweight. It also works with dotenv, and you can pass a custom process.env to the env.from function if you need to.
It's mentioned in a few answers already, but here's an example:
Install it using yarn/npm:
npm install env-var --save
Then read variables:
import * as env from 'env-var'
// Read NODE_ENV and verify that:
// 1) it is set using the required() function
// 2) it is either 'dev' or 'prod'
// 3) throw a runtime exception if conditions #1 or #2 fail
const environment = env.get('NODE_ENV').required().asEnum(['dev', 'prod'])
// Intellisense will suggest 'dev' or 'prod'
if (environment === 'dev') {
console.log('yep, this is dev')
} else {
console.log('looks like this is prod')
}
Or another:
import { get } from 'env-var'
// Read the GitHub token. It could be undefined
const githubToken = get('GITHUB_TOKEN').asString()
// Read MAX_CONCURRENCY, or default to 5. Throw an error if it's
// not set to a positive integer value
const concurrencyLimit = get('MAX_CONCURRENCY').default(5).asIntPositive()
function callGitApi (token: string, concurrency: number) { /* implementation */ }
// TS Error: Argument of type 'string | undefined' is not assignable to
// parameter of type 'string'.
callGitApi(githubToken, concurrencyLimit)