when I run a test in node.js with mocha, how I can set temporal environment variables?
in a module, I have a variable depending of a environment variable
var myVariable = proccess.env.ENV_VAR;
now I use the rewire module,
var rewire = require('rewire');
var myModule = rewire('../myModule');
myModule.__set__('myVariable', 'someValue');
exist a more simple way? without the rewire module?
In your myModule.js file, export a function that takes the variable as an argument eg:
module.exports = function (var) {
// return what you were exporting before
};
Then when you require it, require it like so:
var myModule = require('../myModule')(process.env.ENV_VAR);
My first instinct was to simply set the env var at the top of the test.js before any require statements. However, this may not work for you if you have a module that depends on a env var, and it is required multiple times in the same test run. say you have an env dependent module called mode.js:
module.exports = {
MODE : process.env.ENV_VAR
};
If you add a single test file called bTest.js with
process.env.ENV_VAR= "UNIT_TEST_MODE"
const mode = require('./mode.js')
// describe some tests scenarios that use mode.MODE
...
you will be OK. but if you add a second test file
const mode = require('./mode.js')
// describe some more tests scenarios that use mode.MODE
...
and name it aTest.js, the new file will run first in your suite and mode.MODE will be undefined for all subsequent test js files. The require command won't actually reload the same module multiple times.
Let's assume you aren't able to use the dotenv package in your tests. If so, you can set values on the process.env programmatically in the mocha config file. By default, this is found in .mocharc.json or .mocha.yml, but this can easily be translated to .mocharc.js . Referring to the sample js file here: https://github.com/mochajs/mocha/blob/master/example/config/.mocharc.js
So your .mocharc.js could be
"use strict";
process.env.ENV_VAR = "UNIT_TEST_MODE";
// end of .mocharc.js
and ENV_VAR will be set before mocha requires or runs any of your modules.
Even if you are using dotenv , you can choose to flip set other dotenv option from inside your mochajs config that you might not want to set on your local dev server's .env file. That way, your .env.mocha vars will be available to individual modules that don't require dotenv.
"use strict";
require('dotenv').config({ debug: process.env.DEBUG, { path: '/full/custom/path/to/.env.mocha' } })`.
// end of .mocharc.js
Although in the second case, you may be better off just setting the dotenv env path as part of the test command in your package.json:
node -r dotenv/config /node_modules/mocha/bin/_mocha dotenv_config_path=/full/custom/path/to/.env.mocha
Related
For my testing framework I'm using dotenv to read and initialize environment variables for different test environments. Now I want to use a specific environment variable value as a part of the value and the name of another environment variables.
So, I set the env var export INSTANCE=1 before initializing the env file with dotenv.
This is my env file with all the env vars I want to initialize with dotenv:
# INSTANCE as part of env var value
MY_DOMAIN="http://mypage/"$INSTANCE""
# INSTANCE as part of env var name
SUBDOMAIN"$INSTANCE"="/mysubdomain"
And this metric is also not working:
# INSTANCE as part of env var value
MY_DOMAIN=http://mypage/${INSTANCE}
# INSTANCE as part of env var name
SUBDOMAIN${INSTANCE}=/mysubdomain
This is possible doing it via bash with
export MY_DOMAIN="http://mypage/"$INSTANCE"" SUBDOMAIN"$INSTANCE"="/mysubdomain"
echo $MY_DOMAIN
http://mypage/1
echo $SUBDOMAIN1
/mysubdomain
But using dotenv it is not working for me. I have the following code:
const dotenv = require("dotenv");
var result = dotenv.config({ path: '/path/to/my/env/file' });
console.log(result.parsed);
The console.log produces the following result:
{
MY_DOMAIN:'http://mypage/"$INSTANCE"'
}
So, the env var INSTANCE can not be used, the value will be ignored. Are there any solution to solve my issue?
I have found a solution using dotenv-expand :)
The documentation for Vue CLI 3 says here https://cli.vuejs.org/guide/mode-and-env.html#using-env-variables-in-client-side-code:
You can have computed env vars in your vue.config.js file. They still need to be prefixed with VUE_APP_. This is useful for version info process.env.VUE_APP_VERSION = require('./package.json').version
This is exactly what I want to do. But I couldn't find out how to actually define the env var there in vue.config.js. I tried:
module.exports = {
process.env.VUE_APP_VERSION: require("../package.json").version,
...
}
But it just produces an error:
ERROR SyntaxError: Unexpected token .
/Users/lhermann/htdocs/langify/frontend/vue.config.js:2
process.env.VUE_APP_VERSION: require("../package.json").version,
^
Does anyone know?
The environment variables are not part of the config export, you just set them in the vue.config.js file, eg
process.env.VUE_APP_VERSION = require('./package.json').version
module.exports = {
// other config, eg configureWebpack
}
I've raised a feature-request to get an example added to the docs ~ https://github.com/vuejs/vue-cli/issues/2864
Common Environment Variables:
According to Environment Variables and Modes documentation, you can specify env variables by placing .env files in your project root.
The variables will automatically be accessible under process.env.variableName in your project. Loaded variables are also available to all vue-cli-service commands, plugins and dependencies.
.env # loaded in all cases
.env.local # loaded in all cases, ignored by git
.env.[mode] # only loaded in specified mode
.env.[mode].local # only loaded in specified mode, ignored by git
Your .env file(s) should look like this:
VUE_APP_MY_ENV_VARIABLE=value
VUE_APP_ANOTHER_VARIABLE=value
Note that only variables that start with VUE_APP_ will be statically embedded into the client bundle with webpack.DefinePlugin.
Computed Environment Variables:
If you want variables that need pre-processing, you can use chainWebpack property of vue.config.js to inject anything you want:
// vue.config.js
module.exports = {
// ...,
chainWebpack: config => {
config.plugin('define').tap(args => {
args[0]['process.env'].APP_VERSION = `"${require("../package.json").version}"`
return args
})
}
// ...
}
Using this method, you can inject anything, with any names you want; you are not bound by the VUE_APP_ limitation.
I have the following in one of my project files:
const baas = process.env.DBID;
console.log('baas', baas);
If I run:
cross-env PORT=4000 NODE_ENV=production WEBPACK_CONFIG=browser_prod,server_prod webpack --colors
My server.js file looks like:
const baas = undefined;
console.log('baas', baas);
As expected. However, I want to be able to set the ID when I run the built app not when I build the app, ie:
DBID=someotherid node dist/server.js
So I need webpack to not convert const baas = process.env.DBID to it's value at build time, but rather leave it as is, so the server.js uses it's value at runtime.
How do I do this?
Note: if I manually edit the built server.js and change undefined to process.env.DBID then the run script works and the app uses the env var from run time, but I don't want to edit files after building.
You are using the wrong target.
By default, webpack builds the application to be run in the browser. This means it will mock native node functions like path fs and process
Your target is node, so there is no need to mock these.
Add this to your webpack.config.js
module.exports = {
target: 'node'
};
https://webpack.js.org/concepts/targets/#usage
What you need is process.argv not process.env:
// server.js
const baas = process.argv[0];
console.log('baas', baas);
Then:
node dist/server.js baas_value
For convenience, you can use this module https://www.npmjs.com/package/yargs
I was able to prevent Webpack from converting process.env by accessing it indirectly like this:
const processText = "process";
const _process = global[processText];
app.listen(_process.env.PORT || 2000);
You need to get process indirectly instead of env because the process variable is defined by webpack to be something like /* provided dependency */ var process = __webpack_require__(/*! process/browser */ "process/browser");
Is it possible to define a global variable with webpack to result something like this:
var myvar = {};
All of the examples I saw were using external file require("imports?$=jquery!./file.js")
There are several way to approach globals:
1. Put your variables in a module.
Webpack evaluates modules only once, so your instance remains global and carries changes through from module to module. So if you create something like a globals.js and export an object of all your globals then you can import './globals' and read/write to these globals. You can import into one module, make changes to the object from a function and import into another module and read those changes in a function. Also remember the order things happen. Webpack will first take all the imports and load them up in order starting in your entry.js. Then it will execute entry.js. So where you read/write to globals is important. Is it from the root scope of a module or in a function called later?
config.js
export default {
FOO: 'bar'
}
somefile.js
import CONFIG from './config.js'
console.log(`FOO: ${CONFIG.FOO}`)
Note: If you want the instance to be new each time, then use an ES6 class. Traditionally in JS you would capitalize classes (as opposed to the lowercase for objects) like
import FooBar from './foo-bar' // <-- Usage: myFooBar = new FooBar()
2. Use Webpack's ProvidePlugin.
Here's how you can do it using Webpack's ProvidePlugin (which makes a module available as a variable in every module and only those modules where you actually use it). This is useful when you don't want to keep typing import Bar from 'foo' again and again. Or you can bring in a package like jQuery or lodash as global here (although you might take a look at Webpack's Externals).
Step 1. Create any module. For example, a global set of utilities would be handy:
utils.js
export function sayHello () {
console.log('hello')
}
Step 2. Alias the module and add to ProvidePlugin:
webpack.config.js
var webpack = require("webpack");
var path = require("path");
// ...
module.exports = {
// ...
resolve: {
extensions: ['', '.js'],
alias: {
'utils': path.resolve(__dirname, './utils') // <-- When you build or restart dev-server, you'll get an error if the path to your utils.js file is incorrect.
}
},
plugins: [
// ...
new webpack.ProvidePlugin({
'utils': 'utils'
})
]
}
Now just call utils.sayHello() in any js file and it should work. Make sure you restart your dev-server if you are using that with Webpack.
Note: Don't forget to tell your linter about the global, so it won't complain. For example, see my answer for ESLint here.
3. Use Webpack's DefinePlugin.
If you just want to use const with string values for your globals, then you can add this plugin to your list of Webpack plugins:
new webpack.DefinePlugin({
PRODUCTION: JSON.stringify(true),
VERSION: JSON.stringify("5fa3b9"),
BROWSER_SUPPORTS_HTML5: true,
TWO: "1+1",
"typeof window": JSON.stringify("object")
})
Use it like:
console.log("Running App version " + VERSION);
if(!BROWSER_SUPPORTS_HTML5) require("html5shiv");
4. Use the global window object (or Node's global).
window.foo = 'bar' // For SPA's, browser environment.
global.foo = 'bar' // Webpack will automatically convert this to window if your project is targeted for web (default), read more here: https://webpack.js.org/configuration/node/
You'll see this commonly used for polyfills, for example: window.Promise = Bluebird
5. Use a package like dotenv.
(For server side projects) The dotenv package will take a local configuration file (which you could add to your .gitignore if there are any keys/credentials) and adds your configuration variables to Node's process.env object.
// As early as possible in your application, require and configure dotenv.
require('dotenv').config()
Create a .env file in the root directory of your project. Add environment-specific variables on new lines in the form of NAME=VALUE. For example:
DB_HOST=localhost
DB_USER=root
DB_PASS=s1mpl3
That's it.
process.env now has the keys and values you defined in your .env file.
var db = require('db')
db.connect({
host: process.env.DB_HOST,
username: process.env.DB_USER,
password: process.env.DB_PASS
})
Notes
Regarding Webpack's Externals, use it if you want to exclude some modules from being included in your built bundle. Webpack will make the module globally available but won't put it in your bundle. This is handy for big libraries like jQuery (because tree shaking external packages doesn't work in Webpack) where you have these loaded on your page already in separate script tags (perhaps from a CDN).
I was about to ask the very same question. After searching a bit further and decyphering part of webpack's documentation I think that what you want is the output.library and output.libraryTarget in the webpack.config.js file.
For example:
js/index.js:
var foo = 3;
var bar = true;
webpack.config.js
module.exports = {
...
entry: './js/index.js',
output: {
path: './www/js/',
filename: 'index.js',
library: 'myLibrary',
libraryTarget: 'var'
...
}
Now if you link the generated www/js/index.js file in a html script tag you can access to myLibrary.foo from anywhere in your other scripts.
Use DefinePlugin.
The DefinePlugin allows you to create global constants which can be
configured at compile time.
new webpack.DefinePlugin(definitions)
Example:
plugins: [
new webpack.DefinePlugin({
PRODUCTION: JSON.stringify(true)
})
//...
]
Usage:
console.log(`Environment is in production: ${PRODUCTION}`);
You can use define window.myvar = {}.
When you want to use it, you can use like window.myvar = 1
DefinePlugin doesn't actually define anything. What it does is replace variables that exist in your bundle code. If the variable doesn't exist in your code, it will do nothing. So it doesn't create global variables.
In order to create a global variable, write it in your code:
window.MyGlobal = MY_GLOBAL;
And use DefinePlugin to replace MY_GLOBAL with some code:
new webpack.DefinePlugin({
'MY_GLOBAL': `'foo'`,
// or
'MY_GLOBAL': `Math.random()`,
}),
Then your output JS will be like this:
window.MyGlobal = 'foo';
// or
window.MyGlobal = Math.random();
But MY_GLOBAL will never actually exist at runtime, because it is never defined. So that's why DefinePlugin has a misleading name.
I solved this issue by setting the global variables as a static properties on the classes to which they are most relevant. In ES5 it looks like this:
var Foo = function(){...};
Foo.globalVar = {};
You may hit this issue, when triing bundle < script > tag js files in some old project.
Do not use webpack for this, it may be even impossible if joining 50+ libraries like jquery and then figuring out all global variables or if they used nested require. I would advice to simply use uglify js instead , which drops all this problems in 2 commands.
npm install uglify-js -g
uglifyjs --compress --mangle --output bundle.js -- js/jquery.js js/silly.js
I'm using the expect.js library with my mocha unit tests. Currently, I'm requiring the library on the first line of each file, like this:
var expect = require('expect.js');
describe('something', function () {
it('should pass', function () {
expect(true).to.be(true); // works
});
});
If possible, I'd like to remove the boilerplate require code from the first line of each file, and have my unit tests magically know about expect. I thought I might be able to do this using the mocha.opts file:
--require ./node_modules/expect.js/index.js
But now I get the following error when running my test:
ReferenceError: expect is not defined
This seems to make sense - how can it know that the reference to expect in my tests refers to what is exported by the expect.js library?
The expect library is definitely getting loaded, as if I change the path to something non-existent then mocha says:
"Error: Cannot find module './does-not-exist.js'"
Is there any way to accomplish what I want? I'm running my tests from a gulp task if perhaps that could help.
You are requiring the module properly but as you figured out, the symbols that the module export won't automatically find themselves into the global space. You can remedy this with your own helper module.
Create test/helper.js:
var expect = require("expect.js")
global.expect = expect;
and set your test/mocha.opts to:
--require test/helper
While Louis's answer is spot on, in the end I solved this with a different approach by using karma and the karma-chai plugin:
Install:
npm install karma-chai --save-dev
Configure:
karma.set({
frameworks: ['mocha', 'chai']
// ...
});
Use:
describe('something', function () {
it('should pass', function () {
expect(true).to.be(true); // works
});
});
Thanks to Louis answer and a bit of fiddling around I sorted out my test environment references using mocha.opts. Here is the complete setup.
My project is a legacy JavaScript application with a lot of "plain" js files which I wish to reference both in an html file using script tags and using require for unit testing with mocha.
I am not certain that this is good practice but I am used to Mocha for unit testing in node project and was eager to use the same tool with minimal adaptation.
I found that exporting is easy:
class Foo{...}
class Bar{...}
if (typeof module !== 'undefined') module.exports = { Foo, Bar };
or
class Buzz{...}
if (typeof module !== 'undefined') module.exports = Buzz;
However, trying to use require in all the files was an issue as the browser would complain about variables being already declared even when enclosed in an if block such as:
if (typeof require !== 'undefined') {
var {Foo,Bar} = require('./foobar.js');
}
So I got rid of the require part in the files and set up a mocha.opts file in my test folder with this content. The paths are relative to the root folder:
--require test/mocha.opts.js
mocha.opts.js content. The paths are relative to the location of the file:
global.assert = require('assert');
global.Foo = require("../foobar.js").Foo;
global.Bar = require("../foobar.js").Bar;
global.Buzz = require("../buzz.js");