返回

Vitest + Knex 测试遭遇 MySQL 死锁?试试这个解决方案!

mysql

Vitest + Knex 测试遭遇 MySQL 死锁?这篇帮你解决!

在使用 Vitest 和 Knex 这对黄金搭档进行集成测试时,你是否曾被突如其来的 MySQL 死锁问题打乱节奏?别担心,你不是一个人!本文将带你抽丝剥茧,分析这个问题的根源,并提供一种简洁有效的解决方案,助你摆脱测试瓶颈,迈向高效测试之路。

死锁迷雾:揭开并发冲突的面纱

想象一下,你的测试用例们就像一群急着抢占资源的小精灵。每个测试文件都迫不及待地开启一个新的数据库连接,并开启自己的事务,准备大展身手。然而,当多个测试用例同时执行,尤其是进行 alter table 等修改数据库结构的操作时,问题就出现了:这些小精灵可能会为了争夺同一个资源而陷入僵局,谁也不肯退让,最终导致死锁,测试进程也随之卡住。

你可能会问:我的代码中明明在 beforeAll 禁用了外键约束,并在 afterAll 重新启用了,怎么会出现死锁呢? 问题就出在这里!当多个测试用例并发执行时,这个看似安全的策略却暗藏风险。每个测试用例都试图修改外键约束,导致它们相互干扰,最终引发死锁。

破局之道:集中管理,化解资源争夺战

如何才能避免这些小精灵们互相打架呢?答案是:制定规则,让它们井然有序地工作!Vitest 提供了 beforeAllafterAll 钩子,正好可以用来集中管理数据库连接和事务,化解资源争夺战。

第一步:创建全局测试数据库配置

首先,我们需要创建一个全局的数据库配置文件,就像为小精灵们指定一个公共的游乐场:

// ./test/db.ts
import knex from 'knex';

const testDbConfig = {
  client: 'mysql2',
  connection: {
    host: process.env.DB_HOST || 'localhost',
    user: process.env.DB_USER || 'root',
    password: process.env.DB_PASSWORD || 'password',
    database: process.env.DB_DATABASE || 'test_db',
  },
};

export default knex(testDbConfig);

第二步:修改测试文件

接下来,我们需要修改测试文件,让所有测试用例都使用同一个数据库连接和事务,就像让小精灵们在同一个游乐区域内玩耍:

import db from './test/db'; 
import EntityFactory from '../../../common/__tests__/entityFactory';
import { workspaceRepository } from '../workspaceRepository';

describe('workspaceRepository', () => {
  let trx;

  beforeAll(async () => {
    trx = await db.transaction(); 
    await EntityFactory.createWorkspace(trx, 1, 1, 'workspace1', 'description', 'url'); 
  });

  afterAll(async () => {
    await trx.rollback(); 
  });

  test('createWorkspace', async () => {
    const workspace = await workspaceRepository.createWorkspace(trx, {
      ownerId: 3,
      name: 'workspace5',
      description: 'description',
      avatarUrl: 'url',
    });
    expect(workspace.id).not.toBeNull();
    const workspaces = await trx.select('*').from('workspaces');
    expect(workspaces).toHaveLength(2);
    await EntityFactory.deleteWorkspaces(trx, [workspace.id]); 
  });

  test('getAllUserWorkspaces', async () => {
    const workspaces = await workspaceRepository.findAllUserWorkspaces(trx, 1);
    expect(workspaces).toHaveLength(1);
  });

  // more tests 

}, 15000);

通过以上修改,我们实现了以下目标:

  • 共享数据库连接和事务: 所有测试用例共享同一个数据库连接和事务,就像小精灵们在同一个游乐区域内玩耍,避免了因争夺资源而引发的死锁问题。
  • 事务回滚:afterAll 中使用 trx.rollback() 回滚事务,就像每次游戏结束后,都会将游乐区域恢复到初始状态,确保每个测试用例结束后数据库恢复到初始状态,避免测试数据相互干扰,保证测试结果的独立性。

测试无忧:拥抱高效稳定的测试体验

通过集中管理数据库连接和事务,我们成功地解决了 Vitest + Knex 测试中常见的 MySQL 死锁问题。这种方法不仅提高了测试效率,也保证了测试结果的准确性和可靠性,为构建稳定可靠的应用程序奠定了坚实的基础。

常见问题解答

  1. 问:为什么我的测试用例还是偶尔会出现死锁?

    答: 这可能是因为你的测试用例中存在隐蔽的资源竞争关系。尝试分析你的测试用例逻辑,找出潜在的并发冲突点,并进行相应的调整。

  2. 问:集中管理数据库连接后,测试效率是否会受到影响?

    答: 相反,集中管理数据库连接可以减少连接创建和销毁的开销,提高测试效率。

  3. 问:除了集中管理数据库连接和事务,还有哪些方法可以避免死锁?

    答: 其他方法包括:优化数据库设计、合理使用索引、避免长事务等。

  4. 问:trx.rollback() 会影响测试数据的持久化吗?

    答: 是的,trx.rollback() 会回滚事务,测试数据不会被持久化到数据库中。

  5. 问:如何选择合适的数据库连接池配置?

    答: 数据库连接池配置需要根据实际情况进行调整,例如并发用户数、数据库性能等。建议参考相关文档进行配置。