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

如何用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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.20 11:43:23