【发布时间】:2020-05-07 00:57:05
【问题描述】:
简介
我在 Jest 测试框架中使用的数据库 Promises 卡住了。事情以错误的顺序运行,并且在我最近的一些更改之后,Jest 没有正确完成,因为没有处理未知的异步操作。我对 Node/Jest 还很陌生。
这就是我想要做的。我在多个 Docker 容器环境中设置 Jest 以调用内部 API 以测试其 JSON 输出,并运行服务函数以查看它们对测试环境 MySQL 数据库所做的更改。为此,我是:
- 使用
setupFilesAfterEnvJest 配置选项指向一个安装文件,我认为应该首先运行该文件 - 使用安装文件销毁测试数据库(如果存在),重新创建它,然后创建一些表
- 使用
mysql2/promise对数据库进行操作 - 在测试中使用
beforeEach(() => {})截断所有表,准备插入每个测试的数据,以便测试不相互依赖
我可以确认 Jest 的设置文件在第一个(也是唯一一个)测试文件之前运行,但奇怪的是测试文件中的 Promise catch() 似乎在 finally 之前抛出设置文件。
我会先写下我的代码,然后推测我隐约怀疑的问题。
代码
这是设置文件,简洁明了:
// Little fix for Jest, see https://stackoverflow.com/a/54175600
require('mysql2/node_modules/iconv-lite').encodingExists('foo');
// Let's create a database/tables here
const mysql = require('mysql2/promise');
import TestDatabase from './TestDatabase';
var config = require('../config/config.json');
console.log('Here is the bootstrap');
const initDatabase = () => {
let database = new TestDatabase(mysql, config);
database.connect('test').then(() => {
return database.dropDatabase('contributor_test');
}).then(() => {
return database.createDatabase('contributor_test');
}).then(() => {
return database.useDatabase('contributor_test');
}).then(() => {
return database.createTables();
}).then(() => {
return database.close();
}).finally(() => {
console.log('Finished once-off db setup');
});
};
initDatabase();
config.json 只是用户名/密码,不值得在这里显示。
如您所见,这段代码使用了一个实用数据库类,即:
export default class TestDatabase {
constructor(mysql, config) {
this.mysql = mysql;
this.config = config;
}
async connect(environmentName) {
if (!environmentName) {
throw 'Please supply an environment name to connect'
}
if (!this.config[environmentName]) {
throw 'Cannot find db environment data'
}
const config = this.config[environmentName];
this.connection = await this.mysql.createConnection({
host: config.host, user: config.username,
password: config.password,
database: 'contributor'
});
}
getConnection() {
if (!this.connection) {
throw 'Database not connected';
}
return this.connection;
}
dropDatabase(database) {
return this.getConnection().query(
`DROP DATABASE IF EXISTS ${database}`
);
}
createDatabase(database) {
this.getConnection().query(
`CREATE DATABASE IF NOT EXISTS ${database}`
);
}
useDatabase(database) {
return this.getConnection().query(
`USE ${database}`
);
}
getTables() {
return ['contribution', 'donation', 'expenditure',
'tag', 'expenditure_tag'];
}
/**
* This will be replaced with the migration system
*/
createTables() {
return Promise.all(
this.getTables().map(table => this.createTable(table))
);
}
/**
* This will be replaced with the migration system
*/
createTable(table) {
return this.getConnection().query(
`CREATE TABLE IF NOT EXISTS ${table} (id INTEGER)`
);
}
truncateTables() {
return Promise.all(
this.getTables().map(table => this.truncateTable(table))
);
}
truncateTable(table) {
return this.getConnection().query(
`TRUNCATE TABLE ${table}`
);
}
close() {
this.getConnection().close();
}
}
最后,这是实际测试:
const mysql = require('mysql2/promise');
import TestDatabase from '../TestDatabase';
var config = require('../../config/config.json');
let database = new TestDatabase(mysql, config);
console.log('Here is the test class');
describe('Database tests', () => {
beforeEach(() => {
database.connect('test').then(() => {
return database.useDatabase('contributor_test');
}).then (() => {
return database.truncateTables();
}).catch(() => {
console.log('Failed to clear down database');
});
});
afterAll(async () => {
await database.getConnection().close();
});
test('Describe this demo test', () => {
expect(true).toEqual(true);
});
});
输出
如你所见,我有一些控制台日志,这是他们意想不到的顺序:
- “这是引导程序”
- “这里是测试类”
- 测试到此结束
- “清除数据库失败”
- “已完成一次性数据库设置”
- Jest 报告“Jest 在测试运行完成后一秒钟没有退出。这通常意味着在您的测试中存在未停止的异步操作。”
- Jest 挂起,需要 ^C 退出
我想要:
- “这是引导程序”
- “已完成一次性数据库设置”
- “这里是测试类”
- 调用
truncateTables时没有错误
我怀疑数据库错误是TRUNCATE 操作失败,因为表还不存在。当然,如果命令以正确的顺序运行,它们就会!
注意事项
我最初是导入mysql 而不是mysql/promise,并从 Stack Overflow 上的其他地方发现,如果没有承诺,需要为每个命令添加回调。这会使设置文件变得混乱 - 每个操作连接、删除数据库、创建数据库、使用数据库、创建表、关闭都需要出现在深度嵌套的回调结构中。我可能会这样做,但它有点恶心。
我还尝试使用await 针对所有返回承诺的数据库操作编写设置文件。但是,这意味着我必须将initDatabase 声明为async,我认为这意味着我不能再保证整个安装文件首先运行,这本质上与我现在遇到的问题相同。
我注意到TestDatabase 中的大多数实用程序方法都返回了一个承诺,我对此非常满意。然而connect 是一个奇怪的东西——我希望它来存储连接,所以我对是否可以返回 Promise 感到困惑,因为 Promise 不是连接。我刚刚尝试使用.then() 来存储连接,如下所示:
return this.mysql.createConnection({
host: config.host, user: config.username,
password: config.password
}).then((connection) => {
this.connection = connection;
});
我想知道这是否可行,因为 thenable 链应该等待连接承诺解决,然后再移动到列表中的下一个事物。但是,会产生同样的错误。
我短暂地认为使用两个连接可能会出现问题,以防在一个连接中创建的表在该连接关闭之前无法看到。基于这个想法,也许我应该尝试在设置文件中连接并以某种方式重新使用该连接(例如,通过使用 mysql2 连接池)。但是我的感觉告诉我,这确实是一个 Promise 问题,我需要在 Jest 尝试继续测试执行之前弄清楚如何在设置文件中完成我的 db init。
接下来我可以尝试什么?如果这是一种更好的方法,我愿意放弃mysql2/promise 并回退到mysql,但如果可能的话,我宁愿坚持(并完全理解)承诺。
【问题讨论】:
-
我收到一些场外反馈,
beforeEach()需要返回其内容,以通知 Jest 它正在执行异步操作。我也会试试的。
标签: javascript async-await jestjs es6-promise