Jest cannot test commander help function - jestjs

With jest I'm not able to test commander module functions that result in process exit.
For example, if I pass the --help option or an invalid parameter like -x (see below) process.exit or process.stdout.write are not called as they should looking at the commander sources.
import {Command} from "commander";
let mockExit: jest.SpyInstance;
let mockStdout: jest.SpyInstance;
beforeAll(() => {
mockExit = jest.spyOn(process, "exit").mockImplementation();
mockStdout = jest.spyOn(process.stdout, "write").mockImplementation();
});
afterAll(() => {
mockExit.mockRestore();
mockStdout.mockRestore();
});
test("Ask for help", () => {
// Setup
const save = JSON.parse(JSON.stringify(process.argv));
process.argv = ["--help"]; // Same setting it to "-x"
const program = new Command();
program
.option("-v, --verbose [level]", "verbose level")
.parse(process.argv);
expect(mockExit).toBeCalled();
// expect(mockStdout).toBeCalled();
// Cleanup
process.argv = save;
});
What is strange is that, from the behavior of other tests, process.argv is not restored after this one.
Tests are in typescript and passed through ts-jest.
Any ideas?
Thanks!

I suggest you use .exitOverride(), which is the approach Commander uses in its own tests. This means early "termination" is via a throw rather than exit.
https://github.com/tj/commander.js#override-exit-handling
The first problem though (from comments) is the arguments. Commander expects the parse arguments follow the conventions of node with argv[0] is the application and argv[1] is the script being run, with user parameters after that.
So instead of:
argsToParse = ["--help"];
something like:
argsToParse = ['node", "dummy.js", "--help"];
(No need to modify process.argv as such.)

Related

why i am not able to update env variable in node js

I want to update my env variable in node js, but i am not able to update its env variable, i tried with console.log(process.env.DB_HOST) but still i am getting old value, here i have added my whole code, can anyone please look in to it, and help me to resolve this issue,
function exec_api() {
return new Promise(async function (resolve) {
const execSync = require('child_process').exec;
//let child_process_obj = execSync('DB_HOST='+process.env.UNITTEST_DB_HOST+' DB_DATABASE='+process.env.UNITTEST_DB_DATABASE+' DB_USERNAME='+process.env.UNITTEST_DB_USERNAME+' DB_PASSWORD='+process.env.UNITTEST_DB_PASSWORD+' PORT='+process.env.UNITTEST_SERVICE_PORT+' ./node_modules/.bin/nodemon main.js');
await execSync('export DB_HOST=' + process.env.UNITTEST_DB_HOST);
await execSync('export DB_DATABASE=' + process.env.UNITTEST_DB_DATABASE);
await execSync('export DB_USERNAME=' + process.env.UNITTEST_DB_USERNAME);
await execSync('export DB_PASSWORD=' + process.env.UNITTEST_DB_PASSWORD);
await execSync('export PORT=' + process.env.UNITTEST_API_BACKEND_PORT);
let child_process_obj = await execSync('node main.js');
unittest_api_backend_process_id = child_process_obj.pid;
resolve(true);
});
}
TLDR: Just change process.env
To change, add or delete environment variables, use process.env. The following is test code showing how this works:
In main.js:
console.log(process.env.DB_DATABASE);
In exec.js:
const execSync = require ('child_process').execSync;
process.env.DB_DATABASE = 'foo'; // this is ALL you need to do
console.log(execSync('node main.js').toString('utf8'));
With the two files above, if you run node exec.js you will see foo printed out in the console. This is printed from main.js which inherits the environment from exec.js.
So all you need to do in your code is:
I want to update my env variable in node js, but i am not able to update its env variable, i tried with console.log(process.env.DB_HOST) but still i am getting old value, here i have added my whole code, can anyone please look in to it, and help me to resolve this issue,
function exec_api() {
return new Promise(function (resolve) {
const exec = require('child_process').exec;
// The following is node.js equivalent of bash "export":
process.env.DB_HOST = process.env.UNITTEST_DB_HOST;
process.env.DB_DATABASE = process.env.UNITTEST_DB_DATABASE;
process.env.DB_USERNAME = process.env.UNITTEST_DB_USERNAME;
process.env.DB_PASSWORD = process.env.UNITTEST_DB_PASSWORD;
process.env.PORT = process.env.UNITTEST_SERVICE_PORT;
let child_process_obj = exec('node main.js', {
stdio: ['inherit', 'inherit', 'inherit']
});
unittest_api_backend_process_id = child_process_obj.pid;
resolve(true);
});
}
Note that if you want the promise to return when the main.js ends you need to do:
function exec_api() {
return new Promise(function (resolve) {
const exec = require('child_process').exec;
// The following is node.js equivalent of bash "export":
process.env.DB_HOST = process.env.UNITTEST_DB_HOST;
process.env.DB_DATABASE = process.env.UNITTEST_DB_DATABASE;
process.env.DB_USERNAME = process.env.UNITTEST_DB_USERNAME;
process.env.DB_PASSWORD = process.env.UNITTEST_DB_PASSWORD;
process.env.PORT = process.env.UNITTEST_SERVICE_PORT;
let child_process_obj = exec('node main.js', {
stdio: ['inherit', 'inherit', 'inherit']
});
unittest_api_backend_process_id = child_process_obj.pid;
child_process_obj.on('exit', () => resolve(true));
// ^^^ Cannot use `await` as the API is not promise based
// but event based instead.
});
}
Long story: The full explanation of why export doesn't work
On unixen, environment variables, and indeed, the entire environment including current working directory, root directory (which can be changed via chroot) etc. are not features of shells. They are features of processes.
We may be familiar with the export syntax of some shells to set environment variables for child processes but that is the shell's syntax. It has nothing to do with environment variables themselves. C/C++ for example don't use export instead uses the setenv() function do set environment variables (indeed, internally that's what bash/sh/ksh etc do when implementing export).
In node.js, the mechanism for reading and setting environment variables is via process.env.
Why asking a shell to do it don't work
This is not merely a node.js issue. It also won't work in bash:
In exporter.sh:
#! /bin/bash
export DB_DATABASE=$1
In exec.sh:
#! /bin/bash
./exporter.sh foo
echo $DB_DATABASE ;# does not print "foo"
This is a core security feature of unixen: other users should not be allowed to mess with your process. The way this policy is enforced in the case of the environment is that only a parent process can set the environment of the child process. A child process is not allowed to set the environment of the parent process. The assumption is that the child process belongs to the parent process so you should be allowed to do what you want to a program - but since the parent process (you) don't belong to the child process the child is not allowed to mess with the parent's environment.
That's why your attempt to use export doesn't work. It actually works (the variables are indeed created in the subshell) but is not allowed to change the environment of it's parent (the node.js process)
When you use export in a terminal, it instructs the shell to set environment variables.
When you call exec from your code, you are not running such a shell, with the reason being that it would become a challenge to extract the output of every command.
This makes export an ignored command.
You can solve this by passing an option object to execSync:
execSync('node main.js', {
env: {
DB_HOST: 'localhost',
// More envs...
}
}

How to pass command line arguments to NodeJS launched from an executable script

How to set what would otherwise be command-line arguments to node for a NodeJS process run from a launcher script? (The sh/CMD scripts npm places into node_modules/.bin.)
Plenty of NodeJS libraries / frameworks come with their own runner script, e.g. zeit/micro or moleculer that's usually executed from a npm script. This presents a problem in development, since in my case I want to do the equivalent of:
node --inspect -r ts-node/register -r dotenv-safe/config src/index.ts
(Except, of course, that does nothing since index.ts just exports something for the runner to pick up.)
Is there some "clean", preferably generic (i.e. not specific to a given framework's runner exposing those command line params) way that I'm missing to do this, ideally one that works as a npm script? The only thing that seems like it would work would be for e.g. micro:
node-dev -r ts-node/register ./node_modules/micro-dev/bin/micro-dev.js ./src/index.ts
which is kind of a mouthful from the Redundant Department of Redundancy Department and seems to obviate the point of having those launcher scripts. (It also won't work if the runner spawns other Node processes, but that's not a problem I'm actually having.) I'd like to not have to duplicate what the launcher scripts are already doing. I'm also aware of npx having --node-arg but npx is a whole another can of worms. (On Windows it's five seconds of startup time and one spurious error message just to run a script I already have installed; it also won't find an already installed package if it can't find its .cmd launcher script, e.g. when using Docker to run the dev environment. In short I'd rather not use npx for this.)
To clear up the confusion that seems to crop up in the comments: I want to override the command line parameters that affect the behaviour of the NodeJS runtime itself executing the runner script, not pass parameters to the script itself or to my code. That is, the options listed here: https://nodejs.org/api/cli.html
One option is to write a little wrapper script that uses the current process execPath to run child_process.execFile.
So the sample here is to be able to do
node --expose-http2 --zero-fill-buffers -r ./some-module.js ./test.js
but not actually write that out, instead have wrap.js inject the args:
node ./wrap.js ./test.js
I tested running this via npm in a package.json, and it works fine. I tested that it was working by having some-module.js stick a value on the global object, and then logging it in test.js.
Files involved:
wrap.js
const child_process = require('child_process');
const nodeArgs = ['--expose-http2', '--zero-fill-buffers', '-r', './some-module.js'];
const runTarget = process.argv[2];
console.log('going to wrap', runTarget, 'with', nodeArgs);
const finalArgs = nodeArgs.concat(runTarget).concat(process.argv.slice(2));
const child = child_process.execFile(
process.execPath,
finalArgs,
{
env: process.env,
cwd: process.cwd(),
stdio: 'inherit'
}, (e, stdout, stderr) => {
console.log('process completed');
if (e) {
process.emit('uncaughtException', e);
}
});
child.stdout.pipe(process.stdout);
child.stderr.pipe(process.stderr);
and
some-module.js
global.testval = 2;
and
test.js
console.log('hi guys, did the wrap work?', global.testval)
EDIT: So upon further thought, this solution really only satisfies wrapping the initial runner. But most tools, such as mocha re-spawn a sub process which would then lose this effect. To really get the job done, you can proxy each of the child process calls and somewhat enforce that calls to spawn and such also include your args.
I rewrote the code to reflect this. Here's a new setup:
package.json
{
"scripts": {
"test": "node -r ./ensure-wrapped.js node_modules/mocha/$(npm view mocha bin.mocha) ./test.js"
},
"dependencies": {
"mocha": "^5.1.0"
}
}
ensure-wrapped.js
const child_process = require('child_process');
// up here we can require code or do whatever we want;
global.testvalue = 'hi there'
const customParams = ['--zero-fill-buffers'];
// the code below injects itself into any child process's spawn/fork/exec calls
// so that it propogates
const matchNodeRe = /((:?\s|^|\/)node(:?(:?\.exe)|(:?\.js)|(:?\s+)|$))/;
const ensureWrappedLocation = __filename;
const injectArgsAndAddToParamsIfPathMatchesNode = (cmd, args, params) => {
params.unshift(...customParams);
params.unshift(args);
if (!Array.isArray(args)) { // all child_proc functions do [] optionally, then other params
args = []
params.unshift(args);
}
if (!matchNodeRe.test(cmd)) {
return params;
}
args.unshift(ensureWrappedLocation);
args.unshift('-r');
return params;
}
child_process._exec = child_process.exec;
child_process.exec = (cmd, ...params) => {
// replace node.js node.exe or /path/to/node to inject -r ensure-wrapped.js ...args..
// leaves alone exec if it isn't calling node
cmd = cmd.replace(matchNodeRe, '$1 -r ' + ensureWrappedLocation + ' ');
return child_process._exec(cmd, ...params)
}
child_process._execFile = child_process.execFile;
child_process.execFile = (path, args, ...params) => {
params = injectArgsAndAddToParamsIfPathMatchesNode(path, args, params);
return child_process._execFile(path, ...params)
}
child_process._execFileSync = child_process.execFileSync;
child_process.execFileSync = (path, args, ...params) => {
params = injectArgsAndAddToParamsIfPathMatchesNode(path, args, params);
return child_process._execFileSync(path, ...params);
}
child_process._execSync = child_process.execSync;
child_process.execSync = (cmd, ...params) => {
cmd = cmd.replace(matchNodeRe, '$1 -r ' + ensureWrappedLocation + ' ');
return child_process._exec(bin, ...args)
}
child_process._fork = child_process.fork;
child_process.fork = (module, args, ...params) => {
params = injectArgsAndAddToParamsIfPathMatchesNode(process.execPath, args, params);
return child_process._fork(module, ...params);
}
child_process._spawn = child_process.spawn;
child_process.spawn = (cmd, args, ...params) => {
params = injectArgsAndAddToParamsIfPathMatchesNode(cmd, args, params);
return child_process._spawn(cmd, ...params)
}
child_process._spawnSync = child_process.spawnSync;
child_process.spawnSync = (cmd, args, ...params) => {
params = injectArgsAndAddToParamsIfPathMatchesNode(cmd, args, params);
return child_process._spawnSync(cmd, ...params);
}
test.js
describe('test', () => {
it('should have the global value pulled in by some-module.js', (done) => {
if (global.testvalue !== 'hi there') {
done(new Error('test value was not globally set'))
}
return done();
})
})
Please never put code like this into a node module that's published. modifying the global library functions is pretty bad.
Everything passed in the command line AFTER your nodejs application is parsed into an array called process.argv. So...
node myapp.js foo bar hello 5000
In your nodejs code...
const args = process.argv;
console.log(args[0]);
console.log(args[1]);
console.log(args[2]);
console.log(args[3]);
would yield...
foo
bar
hello
5000
I didnt get clear scenario of your problem,but as your question title ,we can execute the any cmd command from nodejs using npm libraries like:
import Promise from 'bluebird'
import cmd from 'node-cmd'
const getAsync = Promise.promisify(cmd.get, { multiArgs: true, context: cmd })
getAsync('node -v').then(data => {
console.log('cmd data', data)
}).catch(err => {
console.log('cmd err', err)
})

Commander throws error for command with description

Here is a simple example of adding command in nodejs using commander:
'use strict';
const {Command} = require('commander');
const run = () => {
const program = new Command();
console.log('CMD');
program.command('cmd [opts...]')
.action((opts) => {
console.log('OPTS');
});
program.parse(process.argv);
};
run();
In this case everything works fine, but when I'm adding description and options, commander throws an error:
program.command('cmd [opts...]', 'DESCRIPTION', {isDefault: true})
node test-commander.js cmd opts
test-commander-cmd(1) does not exist, try --help
My env:
node v8.9.3
npm 5.3.0
commander 2.12.2
That is the declared behavior of commander. From the npm page under Git-style sub-commands...
When .command() is invoked with a description argument, no .action(callback) should be called to handle sub-commands, otherwise there will be an error. This tells commander that you're going to use separate executables for sub-commands, much like git(1) and other popular tools.
The commander will try to search the executables in the directory of the entry script (like ./examples/pm) with the name program-command, like pm-install, pm-search.
So, when you add a description like you have, it'll assume you have another executable file called test-commander-cmd for the sub command.
If commander's behavior is not what you were expecting, I might recommend looking into a package I published, called wily-cli... only if you're not committed to commander, of course ;)
Assuming your code rests in file.js, your example with wily-cli would look like this...
const cli = require('wily-cli');
const run = () => {
cli
.command('cmd [opts...]', 'DESCRIPTION', (options, parameters) => { console.log(parameters.opts); })
.defaultCommand('cmd');
};
run();
// "node file.js option1 option2" will output "[ 'option1', 'option2' ]"

Using Jest to test a command line tool

I have an app which exposes a script as a command. How do I test this script using jest. More specifically how to execute this script using jest and then apply the corresponding expectations? The script does not export any functions, It just contains a bunch of lines of code which are executed sequentially.
You could wrap your code in a main function, export it, and only run the function when executing the module from the command line, and then write tests for it. A simplified example could be:
// script.js
const toUpper = text => text.toUpperCase();
module.exports.toUpper = toUpper;
// It calls the function only if executed through the command line
if (require.main === module) {
toUpper(process.argv[2]);
}
And then import the toUpper function from the test file
// script.test.js
const { toUpper } = require('./script');
test('tranforms params to uppercase', () => {
expect(toUpper('hi')).toBe('HI');
});
See Node: Accessing the main module

How To Execute Windows Shell Commands (Cmd.exe) with Node JS

I would like to
C:\>ACommandThatGetsData > save.txt
But instead of parsing and saving the data in the console, I would like to do the above command with Node.JS
How to execute a shell command with Node.JS?
Use process.execPath():
process.execPath('/path/to/executable');
Update
I should have read the documentations better.
There is a Child Process Module which allows to execute a child process. You will need either child_process.exec, child_process.execFile or child_process.spawn. All of these are similar in use, but each has its own advantages. Which of them to use depends on your needs.
You could also try the node-cmd package:
const nodeCmd = require('node-cmd');
nodeCmd.get('dir', (err, data, stderr) => console.log(data));
On newer versions of the package, the syntax changed a little:
const nodeCmd = require('node-cmd');
nodeCmd.run('dir', (err, data, stderr) => console.log(data));
I know this question is old, but it helped me get to my solution using promises.
Also see: this question & answer
const util = require('util');
const exec = util.promisify(require('child_process').exec);
async function runCommand(command) {
const { stdout, stderr, error } = await exec(command);
if(stderr){console.error('stderr:', stderr);}
if(error){console.error('error:', error);}
return stdout;
}
async function myFunction () {
// your code here building the command you wish to execute ...
const command = 'dir';
const result = await runCommand(command);
console.log("_result", result);
// your code here processing the result ...
}
// just calling myFunction() here so it runs when the file is loaded
myFunction();

Resources