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

如何在Jest中测试类方法内动态赋值的函数调用?

问题描述

我有一个采用策略模式的类Foo,结构如下:

class Foo {
   constructor() {
      this.doStuff = function() { /* not used really */ }
   }
   create(config) {
      this.type = config.type;

      // assign other variables to this

      this.doStuff = StuffStrategy(config.type);

      this.doStuff();
   }
}

StuffStrategy会根据不同type返回不同策略函数,这些策略函数依赖Foo实例的this上下文:

function StuffStrategy(type) {
   switch(type) {
     case A: return StrategyA;
     case B: return StrategyB;
     // ...
   }
}

function StrategyA() {
  if(this.someVarInFoo) {
    return 'a thing'
  } else 
    return 'a different thing' + this.otherVarInFoo
}

我需要用Jest测试create方法是否调用了doStuff,测试代码如下:

describe('how create method works', () => {
    const instance = new Foo();
    const spy = jest.spyOn(instance, 'doStuff');
    instance.create(config); 
    expect(spy).toBeCalled();
});

但遇到问题:提前创建的spy指向构造函数中定义的默认doStuff,该函数会在create中被替换;若在create调用后创建spy则无法捕获调用。尝试用bind绑定this:this.doStuff = StuffStrategy(config.type).bind(this);,仍未解决。请问如何让测试用例生效?

解决方案

方法1:直接Spy目标策略函数

既然doStuff最终是被赋值为StrategyA/StrategyB这类函数,直接对这些策略函数做spy即可:

describe('how create method works', () => {
    // 假设测试的是type为A的场景
    const strategySpy = jest.spyOn(global, 'StrategyA').mockReturnValue('test');
    const instance = new Foo();
    instance.create({ type: A }); 
    expect(strategySpy).toHaveBeenCalled();
    // 还可以验证返回值或this上下文
    expect(strategySpy.mock.instances[0]).toBe(instance);
});

如果策略函数不是全局的,而是模块导出的,就从对应模块导入后spy。

方法2:修改StuffStrategy的返回值为Spy

拦截StuffStrategy的调用,让它返回一个我们创建的spy,同时保留原函数的逻辑或绑定上下文:

describe('how create method works', () => {
    const instance = new Foo();
    const originalStrategy = StuffStrategy(A);
    const strategySpy = jest.fn(originalStrategy.bind(instance));
    jest.spyOn(global, 'StuffStrategy').mockReturnValue(strategySpy);
    
    instance.create({ type: A }); 
    expect(strategySpy).toHaveBeenCalled();
});

这种方式不需要修改Foo类的结构,同时能准确捕获调用。

方法3:重构Foo类,将doStuff改为原型方法(可选)

如果允许修改类结构,可以把doStuff改成原型方法,然后通过spy原型上的方法来捕获调用:

// 修改后的Foo类
class Foo {
   constructor() {
      // 移除构造函数里的doStuff赋值
   }
   doStuff() { /* 默认实现 */ }
   create(config) {
      this.type = config.type;
      // 替换原型方法的引用
      Foo.prototype.doStuff = StuffStrategy(config.type);
      this.doStuff();
   }
}

// 测试代码
describe('how create method works', () => {
    const instance = new Foo();
    const spy = jest.spyOn(Foo.prototype, 'doStuff');
    instance.create({ type: A }); 
    expect(spy).toHaveBeenCalled();
});

这种方式适合长期维护的代码,让类结构更符合原型模式的规范。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.03 17:05:46