I started to write tests with Jest of (nano)express application. The test starts the server at beforeAll() and closes it at afterAll(). I can see that the code is executed, but the JEST process does not end.
test.js
test('end to end test', async () => {
const polls = await axios.get(`http://localhost:3000/bff/polls/last`);
console.log(polls.data);
expect(polls.data).toBeDefined();
});
beforeAll(() => {
app.listen(3000, '0.0.0.0')
.then(r => logger.info("Server started"));
});
afterAll(() => {
if (app.close())
logger.info("Server stopped");
});
Output from npm run test
Test Suites: 1 failed, 1 total
Tests: 1 failed, 1 total
Snapshots: 0 total
Time: 5.625s
Ran all test suites.
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 run with jest --config jest.config.js --detectOpenHandles the test does not finish as well but there is no error and I need to kill it anyway.
The complete source code is there: https://github.com/literakl/mezinamiridici/blob/master/infrastructure/test/api.int.test.js
I have tested separatelly outside of the tests that nanoexpress will terminate the process with app.close() call. So it is JEST related.
Update: the same behaviour with promises
test('end to end test', () => {
const polls = axios.get(`http://localhost:3000/bff/polls/last`);
return expect(polls).resolves.toBeDefined();
});
Update:
Here you can find minimum reproducible repository: https://github.com/literakl/nano-options.git
I have switched from Axios to GotJS and the trouble is still there. When I run the test with npm run test from command line now, it fails with:
Timeout - Async callback was not invoked within the 20000ms timeout specified by jest.setTimeout.Timeout - Async callback was not invoked within the 20000ms timeout specified by jest.setTimeout.Error
When I start the test from WebStorm there is no error but the process keeps running.
UPDATE
My initial thought was that this is a winston related issue but it appears that jest testEnvironment has to be set to node in order for Axios to run propertly using the axios/lib/adapters/http adapter. You can check a related issue here "detect jest and use http adapter instead of XMLhttpRequest".
Set testEnvironment: 'node' inside jest.config.js.
Update create user test to run the done callback function at the end:
describe("user accounts", () => {
test('create user', async (done) => {
// let response = await axios.get(`${API}/users/1234`);
let response = await axios.get(`${API}/users/1234`, getAuthHeader()); // TODO error with Authorization header
expect(response.data.success).toBeTruthy();
expect(response.data.data).toBeDefined();
let profile = response.data.data;
expect(profile.bio.nickname).toMatch("leos");
expect(profile.auth.email).toMatch("leos#email.bud");
// Call done here to let Jest know we re done with the async test call
done();
});
});
The root cause was an open handle of mongodb client. How did I find it?
1) install leakes-handle library
npm install leaked-handles --save-dev
2) import it to your test
require("leaked-handles");
3) the output
tcp handle leaked at one of:
at makeConnection (infrastructure\node_modules\mongodb\lib\core\connection\connect.js:274:20)
tcp stream {
fd: -1,
readable: true,
writable: true,
address: { address: '127.0.0.1', family: 'IPv4', port: 54963 },
serverAddr: null
}
If you cannot find the root cause, you can kill the JEST explicitelly with
jest --config jest.config.js --runInBand --forceExit
Here is another reason for me. I was using Puppeteer and because my target element was hidden the screenshot method threw error:
const browser = await puppeteer.launch({
headless: true,
executablePath: chromiumPath
});
const page = await browser.newPage();
await page.goto(`file://${__dirname}\\my.html`);
const element = await page.$("#my-element");
// This may throw errors
await element.screenshot({path: snapshotFileName});
await browser.close();
So, I made sure that the browser.close() was called no matter what:
try {
await element.screenshot({path: snapshotFileName});
} finally {
await browser.close();
}
Following works for my integration testing with Express, Nodejs, and Jest. Nothing special in package.json either: "test": "jest --verbose". Cheers
afterAll( async () => {
await mongoose.connection.close();
jest.setTimeout(3000);
});
Tests: 6 passed, 6 total
Snapshots: 0 total
Time: 4.818 s, estimated 5 s
Ran all test suites matching /users.test.js/i.
Related
I've been facing an issue testing an Vue/Nuxt app with WebdriverIO. Mainly, it my tests are always passing. The following code will have an success execution:
it('should open correct app', () => {
Page.open()
browser.pause(1000)
expect(browser).toHaveTitle('Checkout')
browser.pause(1000)
})
As does this one:
it('should open correct app', () => {
Page.open()
browser.pause(1000)
expect(browser).toHaveTitle('Something Completely Different')
browser.pause(1000)
})
My first guess is that Page.open() is loading asynchronous, and the expect condition were being verified before the element existed. But if I try make the function asynchronous, to use await:
it('should open correct app', async () => {
await Page.open()
await browser.pause(1000)
await expect(browser).toHaveTitle('Checkout')
await browser.pause(1000)
})
it gives me an error... of connection.
[Chrome 100.0.4896.60 linux #0-0] 1) Yourapp should open correct app
[Chrome 100.0.4896.60 linux #0-0] net::ERR_CONNECTION_REFUSED at http://localhost:3000/
[Chrome 100.0.4896.60 linux #0-0] net::ERR_CONNECTION_REFUSED at http://localhost:3000/
[Chrome 100.0.4896.60 linux #0-0] Error: net::ERR_CONNECTION_REFUSED at http://localhost:3000/
It happens because my tests are configured with an initialization of Nuxt server on the webdriver config hooks, that is basically inspired in that example:
wdio.conf.ts:
...
async onPrepare () {
if (process.env.NUXT_ENV_CI !== 'true') {
return
}
const rootDir = path.resolve(__dirname, '.')
const nuxtConfig = {
head: {
title: 'Yourapp'
},
dev: false,
rootDir,
modules: ['bootstrap-vue/nuxt']
}
this.nuxtInstance = new nuxt.Nuxt(nuxtConfig)
await new nuxt.Builder(this.nuxtInstance).build()
// its based on Express ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓
await this.nuxtInstance.server.listen(3000, 'localhost')
},
onComplete () {
if (process.env.NUXT_ENV_CI !== 'true') {
return
}
this.nuxtInstance.close()
}
...
and, despite it has been issued and corrected here https://github.com/webdriverio/webdriverio/issues/4042 looks like the asynchronous function is running after the onComplete hook is triggered. That's an issue I need the most to fix, since I'm using Browserstack Automate to test in multiple browsers in CI/CD and it only works when those hooks are correctly executed .
Anyway, I handled that locally running npm run dev on another terminal, what makes the tests run again, but they keep always passing successfully even when they shouldn't .
I'm using
Node 16.14.2
NPM 8.5.0
WebdriverIO 7.16.14
Nuxt 2.15.8
Ubuntu 20.04.4
Example code:
https://github.com/kaabsimas/issue-example
Tests run (and Redis works) perfectly fine in a local environment
Redis works normally even when deployed to production
However Redis connections immediately close when run in Heroku's CI test environment
Technologies used:
Node.js, Express, Mocha, Chai, Supertest
Heroku, Heroku CI, (Heroku) Redis
I'm having the tests run through the Pipeline feature. And then I get the following error output:
-----> Running test command `make test-ci`...
NODE_ENV=test ./node_modules/.bin/db-migrate up --log-level=error --env=test --config=config/database.json --migrations-dir ./db/migrations
NODE_ENV=test ./node_modules/.bin/mocha --exit --ui bdd --reporter spec --timeout 15000 --recursive ./tests/setup.js ./tests/
Redis successfully connected
POST: API /account/phone
Redis set error The client is closed
1) accepts a phone number and generates a PIN
0 passing (15s)
1 failing
1) POST: API /account/phone
accepts a phone number and generates a PIN:
Error: Timeout of 15000ms exceeded. For async tests and hooks, ensure "done()" is called; if returning a Promise, ensure it resolves. (/app/tests/api/account.js)
make: *** [Makefile:66: test-ci] Error 1
-----> test command `make test-ci` failed with exit status 2
My app.json file looks like this
{
"environments": {
"test": {
"scripts": {
"test": "make test-ci"
},
"addons": [
"heroku-redis:in-dyno",
"heroku-postgresql:in-dyno"
]
}
}
}
My Redis connection file looks like this:
let redis = require('redis')
let client = redis.createClient({
url: process.env.REDIS_URL
})
client.on('error', err => console.log('Redis Client Error', err))
client.on('connect', () => {
console.log('Redis successfully connected')
})
The route that's being tested looks like this:
redis.set('phone:keys:' + phone, code, { EX: 60 * 5 }).then(val => {
console.log('did it get set in redis?')
twilio.send(phone, message).then(response => {
res.status(201).json({
ok: true,
data: { body: response.body },
})
})
})
.catch((err) => {
console.log('Redis set error', err.message)
})
In local env (and production), it all works normally. Even when running tests locally. But only in Heroku CI it fails, and I see the "Redis set error The client is closed "
I've written a small crawler with the help of Puppeteer.
Now I'm facing the challenge that my tests are rather slowly (> 3 seconds for each test). I've been able to track it down to the launch function of Puppeteer and the usage of Istanbul/nyc.
If I run the test just with mocha, the tests are finished under 400 ms.
But if I additionally use nyc, the duration of the tests exceeds 3000 ms
All that I'm using is
'use strict';
const puppeteer = require('puppeteer');
module.exports = async function startBrowser() {
const options = {
args: [
// '--no-sandbox',
// '--disable-setuid-sandbox',
// '--disable-dev-shm-usage',
// '--disable-accelerated-2d-canvas',
// '--disable-gpu'
],
headless: false // true
};
return await puppeteer.launch(options);
};
Here is the test I'm using:
'use strict';
/* global describe: false, before: false, it: false,
beforeEach: false, afterEach: false, after: false, window: false, document: false */
const assert = require('assert').strict;
const startBrowser = require('../');
const util = require('util');
describe('Puppeteer', function() {
let pageManager;
it('start the browser', async function() {
this.timeout(10000);
console.time('startBrowser');
const browser = await startBrowser();
console.timeEnd('startBrowser');
assert(browser);
console.time('closeBrowser');
await browser.close();
console.timeEnd('closeBrowser');
});
});
I've created a repository with this code and test here.
nyc _mocha ./test/*.test.js runs in ~3500ms, mocha ./test/*.test.js takes only 130ms.
What I've tried so far:
different combination of include/exclude nyc flags
updating to latest versions of Puppeteer, nyc and mocha
removing my Puppeteer arguments
searching for Puppeteer & Istanbul related issues (with not much success)
trying headless: true
bypassing all proxies, see this puppeteer issue
What can I do to have tests with coverage be as fast as the tests alone?
Using:
Ubuntu 19.04
node.js 10.15.3
I've started to debug Puppeteer and these are my findings:
Puppeteer is unsurprisingly using child_process.spawn() to spawn a new browser
nyc is using spawn-wrap for such child processes
spawn-wrap is reading the whole executable (./node_modules/puppeteer/.local-chromium/linux-686378/chrome-linux/chrome) into memory with fs.readFileSync which is taking an unusually long time to finish
spawn-wraps README delivers some kind of an explanation:
The initial wrap call uses synchronous I/O. Probably you should not be using this script in any production environments anyway. Also, this will slow down child process execution by a lot, since we're adding a few layers of indirection.
For me personally the answer is that I cannot get the same performance for running tests with and without code coverage as long as I use nyc/istanbul.
I've given c8 a shot, and the performance is nearly the same and I can still have code coverage.
Please try this also.
'use strict'
const puppeteer = require('puppeteer')
module.exports = async function startBrowser() {
const options = {
args: [
'--no-sandbox',
'--disable-setuid-sandbox',
'--disable-dev-shm-usage',
'--disable-accelerated-2d-canvas',
'--no-first-run',
'--no-zygote',
'--single-process', // <- this one doesn't works in Windows
'--disable-gpu'
],
headless: true
}
return await puppeteer.launch(options)
}
./Chromium --headless --disable-gpu --remote-debugging-port=9222 --devtools=false
browser = await puppeteer.connect({
browserWSEndpoint: 'ws://127.0.0.1:9222/devtools/browser/........xxxxxx..',
});
use express to hold it
PS: But ,I can't specify the browserWSEndpoint
the url change every time Chromium --headless restart
I have the following simple test setup:
test('what did I do to deserve this', async () => {
expect.assertions(1)
const data = await fetchData() // or fetchData2
expect(data).toBe('peanut butter')
})
async function fetchData () {
return "peanut butter"
}
async function fetchData2 () {
return knex.select('name').from('foos')
}
When I use fetchData jest finishes happily.
But when I use fetchData2 it complains of this:
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.
The data variable does have the result from the db query, and other callers higher in the API resolve the query fine and continue the execution of other statements.
I have tried:
the --detectOpenHandles flag but it doesn't show me anything.
making the expect pass for fetchData2 in case it was the issue described here
passing a done arg to the async function in test. It does exist, but calling it does not fix the warning.
throwing try/catch blocks at it
Thanks for any help on making this happy.
Versions of things:
Node v11.1.0
"jest": "^23.6.0"
"knex": "^0.15.2"
To forcefully close Jest rather than DB Connection :
--forceExit
So my test script looked something like this,
"scripts": {
"test": "jest --forceExit"
}
You need to call knex.destroy() in the end of the test suite to teardown connection pool.
Got these errors:
1.
A worker process has failed to exit gracefully and has been force
exited. This is likely caused by tests leaking due to improper
teardown. Try running with --detectOpenHandles to find leaks.
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.
node:internal/process/promises:246
triggerUncaughtException(err, true /* fromPromise */);
^
[UnhandledPromiseRejection: This error originated either by throwing
inside of an async function without a catch block, or by rejecting a
promise which was not handled with .catch(). The promise rejected with
the reason "Error: Request failed with status code 404".] { code:
'ERR_UNHANDLED_REJECTION' }
##[error]Cmd.exe exited with code '1'.
Example with global setup and teardown:
// setup.js
const knexConfiguration = require('../config/knex')[process.env.NODE_ENV];
const knex = require('knex');
const setup = async () => {
const client = knex(knexConfiguration)
await client.migrate.latest();
await client.destroy();
};
module.exports = setup;
// teardown.js
const knexConfiguration = require('../config/knex')[process.env.NODE_ENV];
const knex = require('knex');
const teardown = async () => {
const client = knex(knexConfiguration)
await client.migrate.rollback();
await client.destroy();
};
module.exports = teardown;
Source:
https://github.com/facebook/jest/issues/7287#issuecomment-510068325
Another example:
dbConnection.js
export default new Sequelize({...}); // The Sequelize instance.
some.spec.js
import dbConnection from './dbConnection';
const { SomeModel } = dbConnection.models;
describe('...', () => {
beforeEach(async () => {
await SomeModel.create({...});
});
...
});
afterAll(async done => {
// Closing the DB connection allows Jest to exit successfully.
dbConnection.close();
done();
});
https://github.com/facebook/jest/issues/7287#issuecomment-434058564
I have this test file running with Jest:
test('this works', () => {
Promise.resolve();
});
test('this also works', (done) => {
setTimeout(done, 100);
});
test('this does not work', () => {
return models.sequelize.sync({force: true, logging: false});
});
test('this also does not work', (done) => {
models.sequelize.sync({force: true, logging: false}).then(() => done());
});
Something is either weird with Sequelize.js or Jest. Ideas?
To clarify: It's not I'm getting failing tests, all 4 tests are green. The latter two will get the database reset, but the Jest process will forever hang even after all test ran. Jest will say: 4 passed, just I have to manually quit with Ctrl + C.
As mentioned in this Github issue: https://github.com/sequelize/sequelize/issues/7953
The sync method actually will only return when the connection idles eventually, so the solution is to:
models.sequelize.sync({force: true, logging: false}).then(() => {
models.sequelize.close();
});