У меня возникли дополнительные проблемы с кодом 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.