Как отлаживать «Jest обнаружил следующий ... открытый дескриптор, который потенциально препятствует завершению Jest» - PullRequest
0 голосов
/ 09 мая 2020

У меня возникли дополнительные проблемы с кодом asyn c в Jest. Мой предыдущий вопрос (для того же проекта) был связан с запуском asyn c кода в Jest bootstrap. Моя новая проблема связана с запуском в тестах вызовов базы данных asyn c. Моя цель - подключиться к службам базы данных и сделать вызовы, чтобы убедиться, что они правильно читают и записывают в базу данных. У меня есть тесты, запущенные в одном контейнере Docker, подключающиеся к экземпляру MySQL в другом контейнере.

Я использую библиотеку mysql2/promise Node, которая, как следует из того же, обертывает базу данных на основе обратных вызовов операции в обещании. Большинство операций - asyn c, за исключением закрытия соединения (и некоторых других). В самом деле, мне интересно, актуально ли это.

Я должен начать с некоторого кода. Вот мой тест:

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);
  });

});

Это просто запускает пару фиктивных тестов. Они ничего не делают, я просто пытаюсь заставить работать хуки до / после. Вот что они должны сделать:

  • beforeAll - подключиться к базе данных один раз (асинхронная операция)
  • afterAll - отключиться от базы данных один раз (синхронная операция в mysql2)
  • beforeEach - запускать database.beforeEachTest() перед каждым тестом, это обрезает таблицы в базе данных (асинхронные операции)

Вот как выглядит 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];
  }
}

Теперь, если я запускаю тесты, они проходят и заканчивают sh, но есть две странности. Во-первых, часть вывода asyn c console.log выводится после сводки теста, поэтому я думаю, что я не обрабатываю asyn c так, как этого хочет 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 останавливается на al oop и требует ^ C, чтобы вернуть приглашение консоли.

Итак, я пробую --detectOpenHandles с текущим кодом, и хотя я не Я получаю следующее:

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 асинхронны c ops, поэтому они должны быть return привязаны к Jest, чтобы Jest знал, что нужно ждать их разрешения.
  • afterAll закрывает db, но это не асинхронно c, поэтому я используя Jest done() здесь, хотя он также не работал, если он был выполнен без done().
  • TestDatabase содержит два основных метода, beforeEachTest и connect, и я был очень осторожен чтобы гарантировать, что они вернут обещания.
  • Я имел склонность к закованным в цепи предметам, ра ther than asyn c -wait, как мне кажется, это яснее. Однако я пробовал asyn c -await в нескольких областях, и это не помогло.
  • Код утилиты, например dropDatabase, createDatabase, setForeignKeyChecks, truncateTables, truncateTable, все возвращаются Обещания.
  • Я читал Jest asyn c docs , и есть много подходов. Главный вывод состоит в том, что если вы тестируете что-то asyn c, обещание должно быть возвращено в Jest, чтобы было выполнено соответствующее ожидание. Мои настоящие тесты синхронны, это просто мои передние хуки asyn c. Размышляя об этом, мне интересно, не в этом ли проблема?

Я новичок в Jest и не слишком опытен в JS asyn c. Каждый раз, когда мне кажется, что я лучше понимаю asyn c, я получаю fre sh Curveball. Тем не менее, мне интересно, не больше ли это странностей шутки, чем трудности понимания raw asyn c.

1 Ответ

0 голосов
/ 10 мая 2020

На данный момент я сбросил --detectOpenHandles и добавил --forceExit. Как следует из названия, он гарантирует, что Jest завершит работу после тестов, даже если он считает, что есть необработанные обещания.

Мне интересно, что эта опция существует - интересно, это означает, что ложные срабатывания являются обычным явлением. В любом случае, мои тесты действительно проходят и не проходят, поэтому я отложу эту проблему на задний план. Мы по-прежнему приветствуем более удовлетворительные ответы, которые не являются временными решениями.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...