You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在TypeScript中规范编写测试?TDD爱好者遇Jest实例化问题

Hey there! 作为同样痴迷TDD+TypeScript+Jest的开发者,我太懂你这种被重复代码和测试实例化坑折腾的痛苦了😉

先帮你理清核心问题:你遇到的Container(也就是我们常说的SUT,被测系统)没有在每个测试前重新实例化,大概率是因为你把实例化逻辑放在了describe的作用域里,而非用Jest的钩子函数来控制执行时机。嵌套describe确实容易让人混淆作用域,但本质上还是要保证测试隔离——每个测试都得有干净的SUT实例,不能让测试之间互相污染状态。

下面给你几个实用的解决方案,既能避免重复代码,又能彻底解决实例化问题:

方案1:用beforeEach钩子函数(最推荐)

不管有没有嵌套describe,beforeEach都会在每个it/test执行前自动运行,完美保证测试隔离,还能把重复的实例化逻辑抽出来。如果有嵌套describe,内层的beforeEach还会和外层的叠加执行,灵活性拉满。

给你写个TypeScript示例:

describe('MyContainer', () => {
  let sut: MyContainer; // 声明变量,作用域覆盖所有子describe和测试用例

  // 外层beforeEach:每个测试前都初始化干净的SUT
  beforeEach(() => {
    sut = new MyContainer(); // 这里可以加通用的初始化参数,比如默认配置
  });

  // 嵌套describe测试「添加元素」模块
  describe('when adding items', () => {
    // 可选:内层beforeEach给当前模块的测试加额外初始化
    beforeEach(() => {
      sut.add('initial-item');
    });

    test('should increase item count by 1', () => {
      sut.add('new-item');
      expect(sut.count()).toBe(2);
    });

    test('should return the correct initial item', () => {
      expect(sut.get(0)).toBe('initial-item');
    });
  });

  // 嵌套describe测试「删除元素」模块
  describe('when removing items', () => {
    test('should decrease item count by 1', () => {
      sut.add('item-to-remove');
      sut.remove(0);
      expect(sut.count()).toBe(0);
    });
  });
});

这样一来,每个测试运行前 sut都会被重新实例化,完全隔离,而且不用在每个describe里重复写new MyContainer()。

方案2:封装成工厂函数(适合动态初始化场景)

如果不同测试需要不同的初始化参数,硬写在beforeEach里不够灵活,可以封装一个工厂函数,在beforeEach或者单个测试里调用:

describe('MyContainer', () => {
  // 工厂函数:生成干净的SUT,支持自定义初始化参数
  const createSut = (initialItems: string[] = []) => {
    return new MyContainer(initialItems);
  };

  test('should start empty with no initial items', () => {
    const sut = createSut();
    expect(sut.count()).toBe(0);
  });

  test('should initialize with given items', () => {
    const sut = createSut(['apple', 'banana']);
    expect(sut.count()).toBe(2);
  });

  describe('when adding items', () => {
    let sut: MyContainer;
    beforeEach(() => {
      sut = createSut(); // 用工厂函数统一初始化
    });

    // ... 这里写具体测试用例
  });
});

这个方法既保持了测试隔离,又能灵活调整SUT的初始状态,同时避免了重复代码。

为什么之前的写法会出问题?

如果你的代码是类似下面这样的,那问题就很明确了:

describe('MyContainer', () => {
  const sut = new MyContainer(); // 只在测试加载阶段实例化一次!

  test('test 1', () => {
    sut.add('item');
  });

  test('test 2', () => {
    // 这里的sut还是test1修改后的实例,状态被污染了!
    expect(sut.count()).toBe(0); // 必然失败!
  });
});

describe里的代码是在测试加载阶段执行的,而非每个测试前。所以sut只会被实例化一次,所有测试共享同一个实例,导致状态互相干扰。而beforeEach是在每个测试执行前运行,所以能保证每次都是全新的实例。

最后再啰嗦一句:不管用哪种方法,核心原则都是每个测试必须独立,不能依赖其他测试的状态——这也是TDD的核心要求之一哦!

内容的提问来源于stack exchange,提问作者ocstat

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.19 09:55:27