如何调试 "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 钩子工作。这些是他们应该做的:

这是 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)

我的观点是,这与我在进行更多测试时遇到的冻结直接相关,我应该在尝试添加更多测试之前解决这个问题。

我已经进行了多次调查循环以确定可能导致此问题的原因,并且对代码进行了多次调整:

我是 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()
          })
        });