如何用Jest模拟SignalR并断言NgRx Effect中的函数调用?
NgRx Effect + SignalR Hub 单元测试断言问题解决方案
1. 正确构建SignalR的链式Mock(核心解决断言问题)
SignalR的HubConnectionBuilder是链式调用模式(new HubConnectionBuilder().withUrl(...).build()),简单mock会断链导致无法断言后续方法调用,需要构建完整模拟调用链:
// 先mock整个signalR库 jest.mock('@microsoft/signalr', () => { // 模拟HubConnection实例的方法(start/stop等) const mockConnection = { start: jest.fn().mockResolvedValue(true), stop: jest.fn().mockResolvedValue(true), // 按需添加你用到的其他方法,比如on/off }; // 模拟HubConnectionBuilder类 return { HubConnectionBuilder: jest.fn().mockImplementation(() => ({ withUrl: jest.fn().mockReturnThis(), // 链式调用返回自身 build: jest.fn().mockReturnValue(mockConnection), // build返回模拟的connection // 若用到其他Builder方法,比如configureLogging,需添加对应的mockReturnThis() })) }; }); // 引入mock后的依赖 import { HubConnectionBuilder } from '@microsoft/signalr'; import { yourEffect } from './your-effect-file'; // 测试用例中断言 test('should build and start SignalR connection on action dispatch', () => { actions$ = of(yourStartHubAction()); yourEffect.connectHubEffect(actions$).subscribe(); // 断言Builder被实例化 expect(HubConnectionBuilder).toHaveBeenCalled(); // 获取Builder实例的mock对象 const builderInstance = (HubConnectionBuilder as jest.Mock).mock.results[0].value; // 断言withUrl被调用(如果你的代码里用到了) expect(builderInstance.withUrl).toHaveBeenCalledWith(expect.any(String)); // 断言build方法被调用 expect(builderInstance.build).toHaveBeenCalled(); // 断言connection的start方法被调用 expect(builderInstance.build().start).toHaveBeenCalled(); });
2. 解决抽离mock对象导致的ReferenceError
jest.mock会被自动提升到文件顶部执行,若把mock对象定义在jest.mock外部,会因变量提升导致jest.mock执行时变量未定义,抛出ReferenceError。解决方式:
- 将mock对象定义放在jest.mock的工厂函数内部(如上述示例)
- 若必须抽离,使用
jest.doMock代替jest.mock,并在测试前手动引入依赖:
// 先定义mock对象 const mockConnection = { start: jest.fn().mockResolvedValue(true), stop: jest.fn().mockResolvedValue(true), }; // 使用jest.doMock,避免自动提升 jest.doMock('@microsoft/signalr', () => ({ HubConnectionBuilder: jest.fn().mockImplementation(() => ({ withUrl: jest.fn().mockReturnThis(), build: jest.fn().mockReturnValue(mockConnection), })) })); // 在测试前手动引入依赖 let HubConnectionBuilder: jest.Mock; beforeEach(() => { jest.resetAllMocks(); ({ HubConnectionBuilder } = require('@microsoft/signalr')); });
3. 解决spyOn时"Property 'build' does not exist"报错
该错误核心原因是:你尝试spyOn的是未被mock的原生HubConnectionBuilder类,或mock对象未正确暴露build属性。解决方式:
- 不要直接spyOn原生类,而是用完整mock的方式,通过mock实例断言方法调用
- 若一定要用spyOn,确保spyOn的是mock后实例的方法:
// 先mock库 jest.mock('@microsoft/signalr', () => ({ HubConnectionBuilder: jest.fn().mockImplementation(() => ({ withUrl: jest.fn().mockReturnThis(), build: jest.fn(), })) })); test('should call build method', () => { const builder = new HubConnectionBuilder(); // spyOn实例的build方法 jest.spyOn(builder, 'build').mockReturnValue({ start: jest.fn() }); // 触发effect逻辑... expect(builder.build).toHaveBeenCalled(); });
内容的提问来源于stack exchange,提问作者Natalia Cáceres
相关产品推荐
相关产品推荐

