What I test: An express server endpoints
My goal: automate API tests in a single script
What I do: I launch the express server in a NodeJS child process and would like to wait for it to be launched before the test suite is run (frisby.js endpoints testing)
What isn't working as expected: Test suite is launched before Promise resolution
I rely on the wait-on
package which server polls and resolves once the resource(s) is/are available.
const awaitServer = async () => {
await waitOn({
resources: [`http://localhost:${PORT}`],
interval: 1000,
}).then(() => {
console.log('Server is running, launching test suite now!');
});
};
This function is used in the startServer function:
const startServer = async () => {
console.log(`Launching server http://localhost:${PORT} ...`);
// npmRunScripts is a thin wrapper around child_process.exec to easily access node_modules/.bin like in package.json scripts
await npmRunScripts(
`cross-env PORT=${PORT} node -r ts-node/register -r dotenv/config src/index.ts dotenv_config_path=.env-tests`
);
await awaitServer();
}
And finally, I use this in something like
describe('Endpoints' () => {
beforeAll(startTestServer);
// describes and tests here ...
});
Anyway, when I launch jest the 'Server is running, launching test suite now!'
console.log never shows up and the test suite fails (as the server isn't running already). Why does jest starts testing as awaitServer
obviously hasn't resolved yet?
The npmRunScripts
function works fine as the test server is up and running a short while after the tests have failed. For this question's sake, here's how npmRunScripts resolves:
// From https://humanwhocodes.com/blog/2016/03/mimicking-npm-script-in-node-js/
const { exec } = require('child_process');
const { delimiter, join } = require('path');
const env = { ...process.env };
const binPath = join(__dirname, '../..', 'node_modules', '.bin');
env.PATH = `${binPath}${delimiter}${env.PATH}`;
/**
* Executes a CLI command with `./node_modules/.bin` in the scope like you
* would use in the `scripts` sections of a `package.json`
* @param cmd The actual command
*/
const npmRunScripts = (cmd, resolveProcess = false) =>
new Promise((resolve, reject) => {
if (typeof cmd !== 'string') {
reject(
new TypeError(
`npmRunScripts Error: cmd is a "${typeof cmd}", "string" expected.`
)
);
return;
}
if (cmd === '') {
reject(
new Error(`npmRunScripts Error: No command provided (cmd is empty).`)
);
return;
}
const subProcess = exec(
cmd,
{ cwd: process.cwd(), env }
);
if (resolveProcess) {
resolve(subProcess);
} else {
const cleanUp = () => {
subProcess.stdout.removeAllListeners();
subProcess.stderr.removeAllListeners();
};
subProcess.stdout.on('data', (data) => {
resolve(data);
cleanUp();
});
subProcess.stderr.on('data', (data) => {
reject(data);
cleanUp();
});
}
});
module.exports = npmRunScripts;
beforeAll()
– Brianabriand