如何在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
相关产品推荐
相关产品推荐

