如何调试 "Jest has detected the following ... open handle potentially keeping Jest from exiting"
How to debug "Jest has detected the following ... open handle potentially keeping Jest from exiting"
我在使用 Jest 中的异步代码时遇到了更多问题。我的 (针对同一个项目)与 运行ning async code in a Jest bootstrap 有关。我的新问题与测试中的 运行ning 异步数据库调用有关。我的目标是连接到数据库服务并进行调用以确保它们正确地读取和写入数据库。我在一个 Docker 容器中测试 运行ning,连接到另一个容器中的 MySQL 实例。
我正在使用 mysql2/promise
Node 库,它同样建议将基于回调的数据库操作包装在 Promise 中。大多数操作都是异步的,除了连接关闭(和其他一些)。确实,我想知道这是否相关。
我应该从一些代码开始。这是我的测试:
import TestDatabase from '../TestDatabase';
var config = require('../../config/config.json');
import FetchDonations from "../../src/services/FetchDonations";
const envName = 'test';
let database = new TestDatabase(config);
// Connect before all tests
beforeAll(() => {
console.log('Connect Jest database');
return database.connect(envName);
});
// Disconnect after all tests
afterAll(async done => {
console.log('Disconnect Jest database');
database.close();
done();
});
describe('Database tests', () => {
// Before every test
beforeEach(() => database.beforeEachTest(envName));
test('Describe this demo test', () => {
console.log('Test #1');
expect(true).toEqual(true);
});
test('Describe this demo test 2', () => {
console.log('Test #2');
expect(true).toEqual(true);
});
});
这只是 运行 几个虚拟测试。他们什么都不做,我只是想让 before/after 钩子工作。这些是他们应该做的:
- beforeAll - 连接一次数据库(异步操作)
- afterAll - 从数据库断开一次(mysql2 中的同步操作)
- beforeEach - 运行
database.beforeEachTest()
在每次测试之前,这个 t运行cates 数据库中的表(异步操作)
这是 TestDatabase
的样子 - 这些是我为帮助数据库测试而编写的实用方法:
const mysql = require('mysql2/promise');
export default class TestDatabase {
constructor(config) {
this.config = config;
}
beforeEachTest(environmentName) {
console.log('Before a test');
return this.setForeignKeyChecks(false).then(() => {
return this.truncateTables();
}).then(() => {
return this.setForeignKeyChecks(true);
}).catch((error) => {
console.log('Failed to clear down database: ' + error);
});
}
connect(environmentName) {
const config = this.getEnvConfig(environmentName);
return mysql.createConnection({
host: config.host, user: config.username,
password: config.password
}).then((connection) => {
this.connection = connection;
return this.useDatabase(environmentName);
}).catch((error) => {
console.log('Failed to connect to the db');
});
}
getConnection() {
if (!this.connection) {
throw 'Database not connected';
}
return this.connection;
}
dropDatabase(environmentName) {
const config = this.getEnvConfig(environmentName);
return this.getConnection().query(
`DROP DATABASE IF EXISTS ${config.database}`
);
}
createDatabase(environmentName) {
const config = this.getEnvConfig(environmentName);
return this.getConnection().query(
`CREATE DATABASE IF NOT EXISTS ${config.database}`
);
}
useDatabase(environmentName) {
const config = this.getEnvConfig(environmentName);
return this.getConnection().query(
`USE ${config.database}`
);
}
setForeignKeyChecks(value) {
// Make injected value safe
var boolStr = value ? '1' : '0';
return this.getConnection().query(
`SET FOREIGN_KEY_CHECKS = ${boolStr}`
);
}
getTables() {
return ['contribution', 'donation', 'expenditure',
'tag', 'expenditure_tag'];
}
truncateTables() {
return Promise.all(
this.getTables().map(table => this.truncateTable(table))
);
}
truncateTable(table) {
return this.getConnection().query(
`TRUNCATE TABLE ${table}`
);
}
/**
* Close is synchronous so there is no returned promise
*/
close() {
this.getConnection().close();
}
getEnvConfig(environmentName) {
if (!environmentName) {
throw 'Please supply an environment name'
}
if (!this.config[environmentName]) {
throw 'Cannot find database environment data'
}
return this.config[environmentName];
}
}
现在,如果我 运行 测试,它们会通过并完成,但有两个奇怪的地方。首先,一些异步 console.log 输出是在测试摘要之后输出的,所以我认为我没有按照 Jest 想要的方式处理异步。换句话说,我认为应该在所有这些之后呈现摘要:
/project/node_modules/.bin/jest tests
console.log
Connect Jest database
at Object.<anonymous> (tests/database/TestDemo.test.js:29:11)
console.log
Before a test
at TestDatabase.beforeEachTest (tests/TestDatabase.js:10:13)
PASS tests/database/TestDemo.test.js
Database tests
✓ Describe this demo test (72ms)
✓ Describe this demo test 2 (58ms)
Test Suites: 1 passed, 1 total
Tests: 2 passed, 2 total
Snapshots: 0 total
Time: 2.118s, estimated 3s
Ran all test suites matching /tests/i.
console.log
Test #1
at Object.<anonymous> (tests/database/TestDemo.test.js:46:13)
console.log
Before a test
at TestDatabase.beforeEachTest (tests/TestDatabase.js:10:13)
console.log
Test #2
at Object.<anonymous> (tests/database/TestDemo.test.js:51:13)
console.log
Disconnect Jest database
at _callee$ (tests/database/TestDemo.test.js:35:11)
如您所见,两个测试的输出都出现在摘要之后,但第一个测试 beforeEach
的输出出现在测试摘要之前。
此外,如果我添加使用数据库的真实测试,我会收到错误提示我有未处理的承诺,我应该尝试 Jest 的未处理承诺检测器 (--detectOpenHandles
)。此外,在那种情况下,Jest 会停止循环并需要 ^C 返回控制台提示。
所以,我正在尝试 --detectOpenHandles
使用当前代码,虽然我没有得到 Jest 冻结,但我得到了以下结果。
Jest has detected the following 1 open handle potentially keeping Jest from exiting:
● TCPWRAP
22 | const config = this.getEnvConfig(environmentName);
23 |
> 24 | return mysql.createConnection({
| ^
25 | host: config.host, user: config.username,
26 | password: config.password
27 | }).then((connection) => {
at new Connection (node_modules/mysql2/lib/connection.js:35:27)
at Object.<anonymous>.exports.createConnection (node_modules/mysql2/index.js:10:10)
at Object.createConnection (node_modules/mysql2/promise.js:230:31)
at TestDatabase.connect (tests/TestDatabase.js:24:18)
at Object.<anonymous> (tests/database/TestDemo.test.js:30:19)
我的观点是,这与我在进行更多测试时遇到的冻结直接相关,我应该在尝试添加更多测试之前解决这个问题。
我已经进行了多次调查循环以确定可能导致此问题的原因,并且对代码进行了多次调整:
afterAll
和 beforeEach
是异步操作,所以它们需要 return
ed 到 Jest,所以 Jest 知道等待它们解决。
afterAll
关闭数据库,但这不是异步的,所以我在这里使用 Jest 的 done()
,尽管如果没有 done()
完成它也不起作用。
TestDatabase
包含两个主要方法,beforeEachTest
和 connect
,我一直非常小心地确保它们 return Promises。
- 我倾向于链式 thenable,而不是 async-await,因为我觉得它更清晰。但是我在几个方面尝试了 async-await,但没有帮助。
- 实用程序代码,如
dropDatabase
、createDatabase
、setForeignKeyChecks
、truncateTables
、truncateTable
所有 return 承诺。
- 我读过 Jest async docs 并且有很多方法。主要的收获是,如果你正在测试异步的东西,promise 应该 returned 到 Jest,这样适当的等待就完成了。我真正的测试是同步的,只是我的 before 钩子是异步的。想了想,是不是这个问题?
我是 Jest 的新手,在 JS 异步方面也不是很有经验。每次我认为我对异步有了更好的理解时,我都会遇到一个新的曲线球。但是,我想知道这是否是 Jest 的怪癖,而不是理解原始异步的困难。
现在,我删除了 --detectOpenHandles
并添加了 --forceExit
。顾名思义,它确保 Jest 在测试后退出,即使它认为有未处理的承诺。
这个选项的存在对我来说很有趣 - 我想知道这是否意味着误报很常见。无论哪种方式,我的测试确实会通过和失败,所以我会把这个问题放在次要位置。仍然非常欢迎更令人满意的答案,但不是解决方法。
最好将服务器连接代码移动到一个函数中到一个单独的文件中,然后将其导出,并在您的玩笑测试中调用它。这可能会阻止错误 open handle potentially keeping jest from exiting
.
使用 --forceExit
很危险,因为它可能会提前终止尚未完成的操作(例如,数据库清理操作),如果它在测试完成后运行。
模型文件是我制作的 class,但 运行 mysql.
不需要它
在 beforeAll
中尝试返回一个 promise 或建立 beforeAll
async await mysql 连接;并且因为 mysql 连接始终处于活动状态,所以它会在错误中抛出一个笑话,提示打开句柄。所以关闭句柄使用 connection.end()
.
describe('make sure that the database exist and the tables exist',function(){
var model = new dbsqlModel();
beforeAll(function(){
return model.setup()
//returns a promise;
})
test('database is called somedbname', function(){
expect(model.settings.database).toBe('somedbname');
});
afterAll(function(){
//model has a property called connection which is mysql.createConnection;
// ends the connection which stopped the error;
model.connection.end()
})
});
我在使用 Jest 中的异步代码时遇到了更多问题。我的
我正在使用 mysql2/promise
Node 库,它同样建议将基于回调的数据库操作包装在 Promise 中。大多数操作都是异步的,除了连接关闭(和其他一些)。确实,我想知道这是否相关。
我应该从一些代码开始。这是我的测试:
import TestDatabase from '../TestDatabase';
var config = require('../../config/config.json');
import FetchDonations from "../../src/services/FetchDonations";
const envName = 'test';
let database = new TestDatabase(config);
// Connect before all tests
beforeAll(() => {
console.log('Connect Jest database');
return database.connect(envName);
});
// Disconnect after all tests
afterAll(async done => {
console.log('Disconnect Jest database');
database.close();
done();
});
describe('Database tests', () => {
// Before every test
beforeEach(() => database.beforeEachTest(envName));
test('Describe this demo test', () => {
console.log('Test #1');
expect(true).toEqual(true);
});
test('Describe this demo test 2', () => {
console.log('Test #2');
expect(true).toEqual(true);
});
});
这只是 运行 几个虚拟测试。他们什么都不做,我只是想让 before/after 钩子工作。这些是他们应该做的:
- beforeAll - 连接一次数据库(异步操作)
- afterAll - 从数据库断开一次(mysql2 中的同步操作)
- beforeEach - 运行
database.beforeEachTest()
在每次测试之前,这个 t运行cates 数据库中的表(异步操作)
这是 TestDatabase
的样子 - 这些是我为帮助数据库测试而编写的实用方法:
const mysql = require('mysql2/promise');
export default class TestDatabase {
constructor(config) {
this.config = config;
}
beforeEachTest(environmentName) {
console.log('Before a test');
return this.setForeignKeyChecks(false).then(() => {
return this.truncateTables();
}).then(() => {
return this.setForeignKeyChecks(true);
}).catch((error) => {
console.log('Failed to clear down database: ' + error);
});
}
connect(environmentName) {
const config = this.getEnvConfig(environmentName);
return mysql.createConnection({
host: config.host, user: config.username,
password: config.password
}).then((connection) => {
this.connection = connection;
return this.useDatabase(environmentName);
}).catch((error) => {
console.log('Failed to connect to the db');
});
}
getConnection() {
if (!this.connection) {
throw 'Database not connected';
}
return this.connection;
}
dropDatabase(environmentName) {
const config = this.getEnvConfig(environmentName);
return this.getConnection().query(
`DROP DATABASE IF EXISTS ${config.database}`
);
}
createDatabase(environmentName) {
const config = this.getEnvConfig(environmentName);
return this.getConnection().query(
`CREATE DATABASE IF NOT EXISTS ${config.database}`
);
}
useDatabase(environmentName) {
const config = this.getEnvConfig(environmentName);
return this.getConnection().query(
`USE ${config.database}`
);
}
setForeignKeyChecks(value) {
// Make injected value safe
var boolStr = value ? '1' : '0';
return this.getConnection().query(
`SET FOREIGN_KEY_CHECKS = ${boolStr}`
);
}
getTables() {
return ['contribution', 'donation', 'expenditure',
'tag', 'expenditure_tag'];
}
truncateTables() {
return Promise.all(
this.getTables().map(table => this.truncateTable(table))
);
}
truncateTable(table) {
return this.getConnection().query(
`TRUNCATE TABLE ${table}`
);
}
/**
* Close is synchronous so there is no returned promise
*/
close() {
this.getConnection().close();
}
getEnvConfig(environmentName) {
if (!environmentName) {
throw 'Please supply an environment name'
}
if (!this.config[environmentName]) {
throw 'Cannot find database environment data'
}
return this.config[environmentName];
}
}
现在,如果我 运行 测试,它们会通过并完成,但有两个奇怪的地方。首先,一些异步 console.log 输出是在测试摘要之后输出的,所以我认为我没有按照 Jest 想要的方式处理异步。换句话说,我认为应该在所有这些之后呈现摘要:
/project/node_modules/.bin/jest tests
console.log
Connect Jest database
at Object.<anonymous> (tests/database/TestDemo.test.js:29:11)
console.log
Before a test
at TestDatabase.beforeEachTest (tests/TestDatabase.js:10:13)
PASS tests/database/TestDemo.test.js
Database tests
✓ Describe this demo test (72ms)
✓ Describe this demo test 2 (58ms)
Test Suites: 1 passed, 1 total
Tests: 2 passed, 2 total
Snapshots: 0 total
Time: 2.118s, estimated 3s
Ran all test suites matching /tests/i.
console.log
Test #1
at Object.<anonymous> (tests/database/TestDemo.test.js:46:13)
console.log
Before a test
at TestDatabase.beforeEachTest (tests/TestDatabase.js:10:13)
console.log
Test #2
at Object.<anonymous> (tests/database/TestDemo.test.js:51:13)
console.log
Disconnect Jest database
at _callee$ (tests/database/TestDemo.test.js:35:11)
如您所见,两个测试的输出都出现在摘要之后,但第一个测试 beforeEach
的输出出现在测试摘要之前。
此外,如果我添加使用数据库的真实测试,我会收到错误提示我有未处理的承诺,我应该尝试 Jest 的未处理承诺检测器 (--detectOpenHandles
)。此外,在那种情况下,Jest 会停止循环并需要 ^C 返回控制台提示。
所以,我正在尝试 --detectOpenHandles
使用当前代码,虽然我没有得到 Jest 冻结,但我得到了以下结果。
Jest has detected the following 1 open handle potentially keeping Jest from exiting:
● TCPWRAP
22 | const config = this.getEnvConfig(environmentName);
23 |
> 24 | return mysql.createConnection({
| ^
25 | host: config.host, user: config.username,
26 | password: config.password
27 | }).then((connection) => {
at new Connection (node_modules/mysql2/lib/connection.js:35:27)
at Object.<anonymous>.exports.createConnection (node_modules/mysql2/index.js:10:10)
at Object.createConnection (node_modules/mysql2/promise.js:230:31)
at TestDatabase.connect (tests/TestDatabase.js:24:18)
at Object.<anonymous> (tests/database/TestDemo.test.js:30:19)
我的观点是,这与我在进行更多测试时遇到的冻结直接相关,我应该在尝试添加更多测试之前解决这个问题。
我已经进行了多次调查循环以确定可能导致此问题的原因,并且对代码进行了多次调整:
afterAll
和beforeEach
是异步操作,所以它们需要return
ed 到 Jest,所以 Jest 知道等待它们解决。afterAll
关闭数据库,但这不是异步的,所以我在这里使用 Jest 的done()
,尽管如果没有done()
完成它也不起作用。TestDatabase
包含两个主要方法,beforeEachTest
和connect
,我一直非常小心地确保它们 return Promises。- 我倾向于链式 thenable,而不是 async-await,因为我觉得它更清晰。但是我在几个方面尝试了 async-await,但没有帮助。
- 实用程序代码,如
dropDatabase
、createDatabase
、setForeignKeyChecks
、truncateTables
、truncateTable
所有 return 承诺。 - 我读过 Jest async docs 并且有很多方法。主要的收获是,如果你正在测试异步的东西,promise 应该 returned 到 Jest,这样适当的等待就完成了。我真正的测试是同步的,只是我的 before 钩子是异步的。想了想,是不是这个问题?
我是 Jest 的新手,在 JS 异步方面也不是很有经验。每次我认为我对异步有了更好的理解时,我都会遇到一个新的曲线球。但是,我想知道这是否是 Jest 的怪癖,而不是理解原始异步的困难。
现在,我删除了 --detectOpenHandles
并添加了 --forceExit
。顾名思义,它确保 Jest 在测试后退出,即使它认为有未处理的承诺。
这个选项的存在对我来说很有趣 - 我想知道这是否意味着误报很常见。无论哪种方式,我的测试确实会通过和失败,所以我会把这个问题放在次要位置。仍然非常欢迎更令人满意的答案,但不是解决方法。
最好将服务器连接代码移动到一个函数中到一个单独的文件中,然后将其导出,并在您的玩笑测试中调用它。这可能会阻止错误 open handle potentially keeping jest from exiting
.
使用 --forceExit
很危险,因为它可能会提前终止尚未完成的操作(例如,数据库清理操作),如果它在测试完成后运行。
模型文件是我制作的 class,但 运行 mysql.
不需要它在 beforeAll
中尝试返回一个 promise 或建立 beforeAll
async await mysql 连接;并且因为 mysql 连接始终处于活动状态,所以它会在错误中抛出一个笑话,提示打开句柄。所以关闭句柄使用 connection.end()
.
describe('make sure that the database exist and the tables exist',function(){
var model = new dbsqlModel();
beforeAll(function(){
return model.setup()
//returns a promise;
})
test('database is called somedbname', function(){
expect(model.settings.database).toBe('somedbname');
});
afterAll(function(){
//model has a property called connection which is mysql.createConnection;
// ends the connection which stopped the error;
model.connection.end()
})
});