jest doesn't wait beforeAll resolution to start tests - node.js

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;

I found the solution. After trying almost anything, I didn't realize jest had a timeout setup which defaults at 5 seconds. So I increased this timeout and the tests now wait for the server promise to resolve.
I simply added jest.setTimeout(3 * 60 * 1000); before the test suite.

In my case, it caused by the flaw of the beforeAll part. Make sure the beforeAll doesn't contain any uncaught exceptions, otherwise it will behaves that the testing started without waiting for beforeAll resolves.

After much digging I found a reason for why my beforeAll didn't seem to be running before my tests. This might be obvious to some, but it wasn't to me.
If you have code in your describe outside an it or other beforeX or afterY, and that code is dependent on any beforeX, you'll run into this problem.
The problem is that code in your describe is run before any beforeX. Therefore, that code won't have access to the dependencies that are resolved in any beforeX.
For example:
describe('Outer describe', () => {
let server;
beforeAll(async () => {
// Set up the server before all tests...
server = await setupServer();
});
describe('Inner describe', () => {
// The below line is run before the above beforeAll, so server doesn't exist here yet!
const queue = server.getQueue(); // Error! server.getQueue is not a function
it('Should use the queue', () => {
queue.getMessage(); // Test fails due to error above
});
});
});
To me this seems unexpected, considering that code is run in the describe callback, so my impression was that that callback would be run after all beforeX outside the current describe.
It also seems this behavior won't be changed any time soon: https://github.com/facebook/jest/issues/4097

In newer versions of jest (at least >1.3.1) you can pass a done function to your beforeAll function and call it after everything is done:
beforeAll(async (done) => {
await myAsyncFunc();
done();
})
it("Some test", async () => {
// Runs after beforeAll
})
More discussions here: https://github.com/facebook/jest/issues/1256

Related

How to prevent Jest from running tests as globalSetup is still running?

My global setup code creates a dynamodb table and the global teardown destroys it. The code is just generic dynamodb.createTable() and dynamodb.deleteTable()
My basic test that just wants to return a list of inserted entities:
describe('Test suite', () => {
beforeAll(async () => {
await InsertEntity()
})
test('test', async () => {
const response = await getEntities()
expect(response.success).toBe(true)
})
})
My InsertEntities code:
export const InsertEntities = async () => {
const entity = new Entity({
// some data
})
const result = await entity.save()
console.log('entity', result)
}
But as soon as I run jest, it starts running the tests before the table is up, so it fails horribly. What can I do to stop this behavior?
What have I already tried: Running jest with --runInBand, adding jest.setTimeout() with various different timings going as up as 20secs. Changing the beforeAll to beforeEach. Checking how much time the table needs to go up: 10secs.
I've changed this code to run as setupFilesAfterEnv, but it still crashed. I've written similar code before and it still works! But not this time.
So, what am I missing from jest flow?

Jest run code before and after all of the tests

I would like to run code before and after all the tests, not file wide, test wide. For example;
Before starting the e2e tests, I would like to run the server in test mode with the database. After all, I would like to flush my db and close the processes.
I don't know if it is possible but I also would like to have a global db variable to do tests. What I am currently doing is like this:
describe("Posts Module", () => {
let dbService: DatabaseService;
beforeAll(async () => {
dbService = new DatabaseService();
await dbService.init();
});
it("should give the posts", () => {
supertest(app)
.get("/posts")
.expect(200)
.then(async (response) => {
const dbPosts = await dbService.getPosts();
expect(response.body).toBeDefined();
expect(response.body.posts).toEqual(dbPosts);
});
});
afterAll(async () => {
await flushDb(dbService);
await dbService.close();
});
});
But what I actually want is initing this database service only once before all of the module tests (also starting the server, currently I start the server manually and run the tests afterwards).

How to close Express server inside Jest afterAll hook

I am trying to write integration tests for my Express server using Jest. Since Jest runs tests in parallel (and I would like to avoid running tests in sequence using --runInBand), I am using the get-port library to find a random available port so that different test suites don't have port collisions.
My tests all run successfully, the only problem is that the server is failing to close down properly inside the afterAll hook. This causes Jest to print the following in the console...
Jest did not exit one second after the test run has completed.
This usually means that there are asynchronous operations that weren't stopped in your tests.
Consider running Jest with `--detectOpenHandles` to troubleshoot this issue.
When I use the --detectOpenHandles flag, Jest just hangs after tests complete. Nothing gets printed to the console.
Here is my test code...
let axios = require('axios')
const getPort = require('get-port')
const { app } = require('../../index')
const { Todo } = require('../../models')
// set reference to server to access
// from within afterAll hook
let server
beforeAll(async () => {
const port = await getPort()
axios = axios.create({ baseURL: `http://localhost:${port}` })
server = app.listen(port)
})
afterAll(() => {
server.close()
})
describe('GET /todos', () => {
it('returns a list of todos', async () => {
const { data: todos } = await axios.get('/todos')
todos.forEach(todo => {
expect(Todo.validate(todo)).toEqual(true)
})
})
})
I am on that github thread on this issue. Here is exactly the configuration that works for me. In package.json
"test": "jest --no-cache --detectOpenHandles --runInBand --forceExit",
Here is the configuration in test file
afterEach(async () => {
await server.close();
});
afterAll(async () => {
await new Promise(resolve => setTimeout(() => resolve(), 10000)); // avoid jest open handle error
});
beforeEach(() => {
// eslint-disable-next-line global-require
server = require('../index');
jest.setTimeout(30000);
});
OR you have only afterAll to set timeout and settimeout for each test in the test body individually.That's example below
afterEach(async () => {
await server.close();
});
afterAll(async () => {
await new Promise(resolve => setTimeout(() => resolve(), 10000)); // avoid jest open handle error
});
beforeEach(() => {
// eslint-disable-next-line global-require
server = require('../index');
});
describe('POST /customers', () => {
jest.setTimeout(30000);
test('It creates a customer', async () => {
const r = Math.random()
.toString(36)
.substring(7);
const response = await request(server)
.post('/customers')
.query({
name: r,
email: `${r}#${r}.com`,
password: 'beautiful',
});
// console.log(response.body);
expect(response.body).toHaveProperty('customer');
expect(response.body).toHaveProperty('accessToken');
expect(response.statusCode).toBe(200);
});
});
The root cause is that the express app server is still running after the tests complete. So the solution is to close the server.
In the main server file:
export const server = app.listen(...)
In the test file:
import { server } from './main-server-file'
afterAll(() => {
server.close();
});
Using nodejs 17.4.0, jest 27.5.1, supertest 6.2.2. Running test with
cross-env NODE_OPTIONS=--experimental-vm-modules NODE_ENV=test jest

How can I stop Jest wrapping `console.log` in test output?

I'm making a Jest test with request
describe("GET /user ", () => {
test("It should respond with a array of users", done => {
return request(url, (err, res, body) => {
if (err) {
console.log(err)
} else {
console.log("GET on " + url);
body = JSON.parse(body);
expect(body.length).toBeGreaterThan(0);
done();
}
})
})
});
and it's weird because in the terminal, jest is showing console.log line as part of the output:
...test output...
console.log test/modules/user.test.js:18
GET on https://xpto/api/user
...more test output...
I need to hide the line:
console.log test/modules/user.test.js:18
How to hide console.log line in Jest output?
You can replace Jest's console implementation with the "normal" console like this:
const jestConsole = console;
beforeEach(() => {
global.console = require('console');
});
afterEach(() => {
global.console = jestConsole;
});
If it's a test you've written, why do you even need to log the error? You can rely on jest assertions for that.
If you have no other solution, you can stub out the console.log function like so:
const log = console.log;
console.log = () => {};
/** Test logic goes here **/
console.log = log; // Return things to normal so other tests aren't affected.
You can use jest.spyOn to mock the console methods:
const spy = jest.spyOn(console,'warn').mockReturnValue()
// after you are done with the test, restore the console
spy.mockRestore()
You can create a utility function to mock the console:
function mockConsole(method = 'warn', value){
return jest.spyOn(console,method).mockReturnValue(value).mockRestore
}
const restore = mockConsole() //mock it
// later when you are done
restore() // unmock it
Also, you can combine above code answers with jest.beforeEach to automatically restore the console:
beforeEach(()=>{
// beware that this will restore all mocks, not just the console mock
jest.restoreAllMocks()
})
You need to create your own log function with process.stdout.write or use it instead of console.log because jest is spying on all console functions.
While ago I have been trying to bring to Typescript one utility that I really like in Scala; GivenWhenThen annotations in tests and I encountered the same problem as you. I don't understand why jest prints these "consol.log + line", it doesn't make sense because you can easily find them with Crt + Find and there is no option to switch them off with --silent and --verbose=false options (of course silent will work but it will remove all log so what is the point).
Finally, I came up with:
const colors = require('colors');
const testLog = (str : string) => process.stdout.write(str)
export const Given = (givenMsg: string) => testLog(`\n+ Given ${givenMsg}\n`)
export const When = (whenMsg: string) => testLog(`+ When ${whenMsg}\n`)
export const Then = (thenMsg: string) => testLog(`+ Then ${thenMsg}\n`)
export const And = (andMsg: string) => testLog(`- And ${andMsg}\n`)
export const testCase = (description: string, testFunction: Function) => it (description, () => {
testLog(colors.yellow(`\n\nTest: ${description}\n`))
testFunction()
testLog('\n')
})
Looked like this:
given when then typescript
You could try running jest with the --silent argument.
In package.json, use two scripts:
"scripts": {
"test": "jest --silent",
"testVerbose": "jest"
},

chromeLauncher/Lighthouse: Mocking chained dependencies that return promises using Jest

I have an application that includes a Node script that runs Lighthouse v3 programatically and headlessly (Lighthouse documentation) to test the application's performance.
I wrote some Jest tests for this which pass locally. The tests are not testing Lighthouse itself, but rather how the Node script deals with with the results data returned from Lighthouse. Therefore, the Lighthouse dependency must be mocked.
In the course of doing my testing I have discovered that chrome-launcher, which is invoked by Lighthouse, launches when I'm doing my Jest test. This means I am not mocking this dependency correctly. I thought it was enough to mock Lighthouse and have done so but I am confused about how I can mock the 'thennable' chrome launcher function function.
The launchChromeAndRunLighthouse function below is from the Lighthouse Node documentation.
lighthouse.js
const lighthouse = require('lighthouse');
const chromeLauncher = require('chrome-launcher');
function launchChromeAndRunLighthouse(url, opts, lConfig = null) {
return chromeLauncher
.launch({ chromeFlags: opts.chromeFlags })
.then(chrome => {
const options = { ...opts, port: chrome.port };
return lighthouse(url, options, lConfig).then(results =>
chrome.kill().then(() => results.lhr),
);
});
}
function myFunc(config) {
launchChromeAndRunLighthouse(myAppUrl, config);
// manipulates data returned from launchChromeAndRunLighthouse
return true;
}
module.exports = myFunc;
lighthouse.test.js
import myFunc from './lighthouse';
jest.mock('lighthouse', () =>
jest.fn().mockResolvedValue({
lhr: {
categories: {
performance: {
id: 'performance',
score: 1,
}
},
},
}),
);
// chromeLauncher actually gets invoked by this
describe('myfunc', () => {
it('tests myfunc', async () => {
const result = await myFunc(config);
expect(result).toEqual(true);
});
});
New to Jest and confused at how I can mock chromeLauncher so it's prevented from launching. Thanks
You don't need to mock chromeLauncher, leave it work like it does. Just mocking lighthouse is enough to make tests run and pass. It works as expected in my projects.

Resources