jest-when源码解析:从WhenMock类看参数匹配的实现原理

jest-when源码解析:从WhenMock类看参数匹配的实现原理
jest-when源码解析从WhenMock类看参数匹配的实现原理【免费下载链接】jest-whenJest support for mock argument-matched return values.项目地址: https://gitcode.com/gh_mirrors/je/jest-whenjest-when是一个为Jest提供参数匹配返回值支持的工具库它通过WhenMock类实现了强大的参数匹配功能让开发者能够轻松定义基于不同参数的模拟函数行为。本文将深入解析WhenMock类的核心实现原理帮助你理解参数匹配的工作机制。WhenMock类的核心结构与初始化WhenMock类是jest-when的核心位于src/when.ts文件中它封装了Jest模拟函数的参数匹配逻辑。当我们调用when(fn)时会创建一个WhenMock实例这个实例会与Jest模拟函数关联起来。constructor(fn: jest.MockTReturn, TArgs | jest.SpyInstanceTReturn, TArgs) { this.fn fn; fn.__whenMock__ this; this.__origMock fn.getMockImplementation() || undefined; }在构造函数中WhenMock会保存原始模拟函数的实现以便后续重置时恢复。同时它会在Jest模拟函数上添加__whenMock__属性建立两者之间的关联。参数匹配的核心机制WhenMock通过calledWith和expectCalledWith方法来定义参数匹配规则这两个方法会设置匹配器matchers和期望标志expectCall。定义参数匹配规则calledWith: { (allArgsMatcher: AllArgsMatcherTArgs): WhenMockWithMatchersTReturn, TArgs; (...matchers: ArgumentOrMatcherTArgs): WhenMockWithMatchersTReturn, TArgs; } (...matchers: Matcher[]) { this.__noCalledWithYet false; this._matchers matchers; this._expectCall false; return this; }calledWith方法接受任意数量的匹配器这些匹配器可以是字面量、对象、Jest不对称匹配器或函数匹配器。它会将这些匹配器存储起来供后续调用时进行匹配检查。匹配检查的实现当模拟函数被调用时WhenMock会遍历所有已定义的调用模拟callMocks并检查当前调用的参数是否与存储的匹配器匹配。isMatch args.length matchers.length matchers.reduce(checkArgumentMatchers(expectCall, args), true);匹配检查通过checkArgumentMatchers函数实现它会逐个比较参数与匹配器const checkArgumentMatchers (expectCall: boolean, args: any[]) (match: boolean, matcher: Matcher, i: number): boolean { if (!match) return false; const arg args[i]; const isFunctionMatcher typeof matcher function matcher._isFunctionMatcher; if (expectCall) { if (isFunctionMatcher) { const isMatch matcher(arg); assert.equal(isMatch, true, Failed function matcher: ${matcher.name}(${JSON.stringify(arg)})); } else { expect(arg).toEqual(matcher); } } if (isFunctionMatcher) { return matcher(arg, equals); } return equals(arg, matcher); };这个函数不仅会检查参数是否匹配还会在使用expectCalledWith时进行断言确保模拟函数被正确调用。模拟实现的注册与执行当定义了参数匹配规则后我们可以通过mockReturnValue、mockImplementation等方法来设置匹配成功时的行为。这些方法会将模拟实现注册到callMocks数组中。注册模拟实现private _mockImplementation (mockImplementation: (...args: any[]) any) { this.callMocks this.callMocks .filter((callMock) this._once || callMock.once || !equals(callMock.matchers, this._matchers)) .concat({ matchers: this._matchers, mockImplementation, expectCall: this._expectCall, once: this._once, called: false, id: this.nextCallMockId, callLines: getCallLines() }) .sort((a, b) { if (a.once ! b.once) return a.once ? -1 : 1; return a.id - b.id; }); this.nextCallMockId; this._once false; // 设置模拟函数实现 this.fn.mockImplementation(function (this: any, ...args: any[]) { // 匹配逻辑与执行... }); };_mockImplementation方法会处理模拟实现的注册包括去重、排序等逻辑。值得注意的是一次性模拟once会被优先排序确保它们先被匹配。执行匹配的模拟实现当模拟函数被调用时WhenMock会遍历callMocks数组找到第一个匹配的模拟实现并执行this.fn.mockImplementation(function (this: any, ...args: any[]) { for (let i 0; i instance.callMocks.length; i) { const { matchers, mockImplementation, expectCall, once, called } instance.callMocks[i]; if (once called) continue; // 匹配检查... if (isMatch typeof mockImplementation function) { instance.callMocks[i].called true; return mockImplementation.call(this, ...args); } } // 执行默认实现或原始实现... });这个实现确保了模拟函数在被调用时能够根据参数匹配到正确的模拟实现并执行相应的逻辑。高级特性默认实现与函数匹配器WhenMock还提供了一些高级特性如默认实现和函数匹配器让参数匹配更加灵活。默认实现通过defaultImplementation、defaultReturnValue等方法我们可以为模拟函数设置默认实现当没有匹配的参数规则时执行defaultImplementation: (mockImplementation: (...args: TArgs) TReturn) WhenMockChainTReturn, TArgs (mockImplementation) { this.__noCalledWithYet true; this._matchers [NO_CALLED_WITH_YET]; this._expectCall false; this._mockImplementation(mockImplementation); return this };默认实现使用了一个特殊的匹配器NO_CALLED_WITH_YET确保它只会在没有其他匹配规则时被执行。函数匹配器WhenMock支持使用函数作为匹配器通过when函数或when.allArgs方法创建// 创建单个参数函数匹配器 const isEven when((n: number) n % 2 0); // 创建全参数函数匹配器 const allNumbers when.allArgs((args: any[]) args.every(arg typeof arg number) );函数匹配器在匹配检查时会被直接调用返回true则表示匹配成功。验证与重置机制为了确保测试的可靠性WhenMock提供了验证和重置机制帮助我们检查模拟函数是否被正确调用并在测试之间重置状态。验证模拟调用verifyAllWhenMocksCalled函数会检查所有expectCalledWith定义的模拟是否都被调用export const verifyAllWhenMocksCalled (): void { const [allMocks, calledMocks, uncalledMocks] Array.from(registry).reduce[CallMock[], CallMock[], CallMock[]]((acc, fn) { // 收集所有模拟、已调用模拟和未调用模拟... }, [[], [], []]); const msg Failed verifyAllWhenMocksCalled: ${uncalledMocks.length} not called: ${callLines}; assert.equal(called mocks: ${calledMocks.length}, called mocks: ${allMocks.length}, msg); };重置模拟状态resetAllWhenMocks函数会重置所有WhenMock实例恢复原始模拟实现export const resetAllWhenMocks (): void { registry.forEach(resetWhenMocksOnFn); registry new Set(); }; function resetWhenMocksOnFn(fn: jest.Mock | jest.SpyInstance): void { const whenMock fn.__whenMock__; if (whenMock) { fn.mockImplementation(whenMock.__origMock); fn.__whenMock__ undefined; } registry.delete(fn); }总结通过对WhenMock类的深入解析我们了解了jest-when参数匹配的核心实现原理。WhenMock通过封装Jest模拟函数提供了灵活的参数匹配规则定义、模拟实现注册和执行机制以及验证和重置功能。这些机制共同构成了jest-when强大而易用的API让开发者能够轻松编写基于参数匹配的单元测试。掌握WhenMock的实现原理不仅能帮助我们更好地使用jest-when还能启发我们在其他测试场景中设计类似的参数匹配机制提高测试代码的可读性和可维护性。如果你想深入了解更多细节可以查看项目源码特别是src/when.ts文件中的WhenMock类实现。【免费下载链接】jest-whenJest support for mock argument-matched return values.项目地址: https://gitcode.com/gh_mirrors/je/jest-when创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考