Javascript 如何确保首先运行Jest引导文件? 介绍
我被Jest测试框架中使用的数据库承诺所困扰。事情以错误的顺序运行,在我最近的一些更改之后,Jest没有正确完成,因为没有处理未知的异步操作。我对Node/Jest相当陌生 这就是我要做的。我正在一个多Docker容器环境中设置Jest,以调用内部API来测试它们的JSON输出,并运行服务函数来查看它们对测试环境MySQL数据库所做的更改。为此,我:Javascript 如何确保首先运行Jest引导文件? 介绍,javascript,async-await,jestjs,es6-promise,Javascript,Async Await,Jestjs,Es6 Promise,我被Jest测试框架中使用的数据库承诺所困扰。事情以错误的顺序运行,在我最近的一些更改之后,Jest没有正确完成,因为没有处理未知的异步操作。我对Node/Jest相当陌生 这就是我要做的。我正在一个多Docker容器环境中设置Jest,以调用内部API来测试它们的JSON输出,并运行服务函数来查看它们对测试环境MySQL数据库所做的更改。为此,我: 使用setupfileafterenvJest配置选项指向一个安装文件,我认为应该首先运行该文件 使用安装文件销毁测试数据库(如果存在)、重新创
- 使用
Jest配置选项指向一个安装文件,我认为应该首先运行该文件setupfileafterenv
- 使用安装文件销毁测试数据库(如果存在)、重新创建它,然后创建一些表
- 使用
对数据库执行操作mysql2/promise
- 在测试中使用
截断所有表,以便插入每个测试数据,这样测试就不会相互依赖beforeach(()=>{})
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);
});
});
输出
如您所见,我有一些控制台日志,这是它们意外的顺序:
truncateTables
TRUNCATE
操作失败,因为表还不存在。当然,如果命令以正确的顺序运行,它们会
笔记
我最初导入的是mysql
,而不是mysql/promise
,从堆栈溢出的其他地方发现,如果没有promises,就需要向每个命令添加回调。这将使安装文件变得凌乱-每个操作connect、drop db、create db、use db、create tables、close都需要出现在一个深度嵌套的回调结构中。我可能会做,但有点恶心
我还尝试使用await
编写安装文件,以对抗所有的数据库操作。然而,这意味着我必须将initDatabase
声明为async
,这意味着我不能再保证首先运行整个安装文件,这本质上与我现在遇到的问题相同
我注意到,TestDatabase
中的大多数实用方法都返回了一个承诺,我对此非常满意。然而,connect
是一个奇怪的东西——我想用它来存储连接,所以我对是否可以返回承诺感到困惑,因为承诺不是连接。我刚刚尝试使用.then()
存储连接,如下所示:
return this.mysql.createConnection({
host: config.host, user: config.username,
password: config.password
}).then((connection) => {
this.connection = connection;
});
我想知道这是否可行,因为thenable链应该等待连接承诺解决,然后再转到列表中的下一项。但是,也会产生同样的错误
我简单地认为使用两个连接可能会有问题,因为在一个连接中创建的表在该连接关闭之前无法看到。基于这个想法,也许我应该尝试在安装文件中连接,并以某种方式重新使用该连接(例如,通过使用mysql2连接池)。然而,我的感觉告诉我,这确实是一个承诺问题,我需要在Jest尝试继续测试执行之前,解决如何在安装文件中完成db init
下一步我能试试什么?如果这是一个更好的方法,我愿意放弃
mysql2/promise
并回到mysql
,但如果可能的话,我宁愿坚持(并完全放弃)承诺。你需要等待你的数据库。在之前的中连接()
我有一个解决方案。我还不太了解笑话的微妙之处,我想知道我是否刚刚找到了一个
我的感觉是,因为从引导到Jest没有返回值,所以没有办法通知它需要等待承诺解决后再进行测试。这样做的结果是,在等待测试期间,这些承诺正在得到解决,这造成了一片混乱
换句话说,引导脚本只能用于同步调用
解决方案1
一种解决方案是将表格链从引导文件移动到新的beforeAll()
hook。我将connect
方法转换为返回承诺,因此它的行为与其他方法类似,值得注意的是,我在新钩子和现有钩子中都对承诺链的值进行了return
ed。我相信这告诉Jest,在其他事情发生之前,承诺需要解决
以下是新的测试文件:
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');
beforeAll(() => {
return 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();
}).catch((error) => {
console.log('Init database failed: ' + error);
});
});
describe('Database tests', () => {
beforeEach(() => {
return database.connect('test').then(() => {
return database.useDatabase('contributor_test');
}).then (() => {
return database.truncateTables();
}).catch((error) => {
console.log('Failed to clear down database: ' + error);
});
});
/**
* I wanted to make this non-async, but Jest doesn't seem to
* like receiving a promise here, and it finishes with an
* unhandled async complaint.
*/
afterAll(() => {
database.getConnection().close();
});
test('Describe this demo test', () => {
expect(true).toEqual(true);
});
});
事实上,这可能可以进一步简化,因为不需要关闭和重新打开连接
这是非异步版本
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];
return this.mysql.createConnection({
host: config.host, user: config.username,
password: config.password
}).then(connection => {
this.connection = connection;
});
}
"test": "node ./bin/initdb.js && jest tests"