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

如何Mock含Signal的Angular类?Jest测试遇Getter识别问题

如何Mock返回Signal的Angular Store Getter并验证访问情况

我在Angular应用中用Jest + auto-jest-spies测试依赖其他服务的组件/守卫(比如PermissionGuard),需要Mock基于signalstory实现的AccountStore类。这个Store的isLoggedIn getter返回一个computed Signal,但用createSpyFromClass指定gettersToSpyOn后,无法正确Mock这个getter,也不知道怎么验证Signal是否被访问。


核心问题分析

isLoggedIn是返回Signal函数的getter,而auto-jest-spies默认处理的是返回普通值的getter,无法直接识别并模拟Signal的可调用特性。要解决这个问题,需要让mock的getter返回一个可跟踪的mock函数(模拟Signal的行为),同时验证这个函数的调用情况。


方案1:手动Mock Getter并模拟Signal行为

直接创建mock对象,将isLoggedIn定义为Jest mock函数,完全模拟Signal的调用逻辑,同时支持访问验证。

describe('PermissionGuard', () => {
  let storeMock: Partial<AccountStore> & {
    isLoggedIn: jest.Mock;
  };

  beforeEach(() => {
    // 创建模拟Signal的mock函数
    const mockIsLoggedInSignal = jest.fn(() => false);
    
    storeMock = {
      isLoggedIn: mockIsLoggedInSignal,
      // 按需mock其他属性/方法,比如userInfo
      userInfo: jest.fn(() => ({ userId: null }))
    };

    TestBed.configureTestingModule({
      providers: [
        { provide: AccountStore, useValue: storeMock },
        PermissionGuard,
      ],
    }).compileComponents();
  });

  it('should deny access when user is not logged in', async () => {
    // Arrange:设置Signal返回值
    storeMock.isLoggedIn.mockReturnValue(false);

    // Act:执行守卫逻辑
    const canActivate = await TestBed.inject(PermissionGuard).canActivate(...);

    // Assert:验证结果和Signal访问情况
    expect(canActivate).toBe(false);
    expect(storeMock.isLoggedIn).toHaveBeenCalled(); // 验证Signal被调用(访问)
  });

  it('should allow access when user is logged in', async () => {
    // Arrange
    storeMock.isLoggedIn.mockReturnValue(true);

    // Act
    const canActivate = await TestBed.inject(PermissionGuard).canActivate(...);

    // Assert
    expect(canActivate).toBe(true);
    expect(storeMock.isLoggedIn).toHaveBeenCalled();
  });
});

方案2:结合auto-jest-spies调整Getter Mock

如果想保留auto-jest-spies生成的其他spy功能,可以手动覆盖getter的实现,让它返回一个mock Signal:

import { createSpyFromClass, Spy } from 'auto-jest-spies';

describe('PermissionGuard', () => {
  let storeMock!: Spy<AccountStore>;
  let mockIsLoggedInSignal: jest.Mock;

  beforeEach(() => {
    mockIsLoggedInSignal = jest.fn(() => false);
    
    storeMock = createSpyFromClass(AccountStore, {
      gettersToSpyOn: ['isLoggedIn']
    });

    // 重新定义getter,让它返回mock Signal
    Object.defineProperty(storeMock, 'isLoggedIn', {
      get: () => mockIsLoggedInSignal,
      configurable: true
    });

    TestBed.configureTestingModule({
      providers: [
        { provide: AccountStore, useValue: storeMock },
        PermissionGuard,
      ],
    }).compileComponents();
  });

  it('should test user access correctly', async () => {
    // Arrange
    mockIsLoggedInSignal.mockReturnValue(true);

    // Act
    const result = await TestBed.inject(PermissionGuard).canActivate(...);

    // Assert
    expect(result).toBe(true);
    expect(mockIsLoggedInSignal).toHaveBeenCalled(); // 验证Signal被访问
  });
});

方案3:Mock Store State(贴近真实逻辑)

如果需要模拟state()的返回值,让isLoggedIn的computed Signal基于真实状态计算,可以直接mockstate方法:

describe('PermissionGuard', () => {
  let storeMock: Partial<AccountStore>;

  beforeEach(() => {
    storeMock = {
      // mock state方法,返回不同状态
      state: jest.fn(() => ({ userId: null })),
      // 模拟isLoggedIn的computed逻辑
      get isLoggedIn() {
        return jest.fn(() => !!this.state().userId);
      }
    };

    TestBed.configureTestingModule({
      providers: [
        { provide: AccountStore, useValue: storeMock },
        PermissionGuard,
      ],
    }).compileComponents();
  });

  it('should deny access when userId is null', async () => {
    // Arrange:设置state返回无userId的状态
    (storeMock.state as jest.Mock).mockReturnValue({ userId: null });

    // Act
    const canActivate = await TestBed.inject(PermissionGuard).canActivate(...);

    // Assert
    expect(canActivate).toBe(false);
    expect(storeMock.state).toHaveBeenCalled();
    expect(storeMock.isLoggedIn()).toBe(false);
  });

  it('should allow access when userId exists', async () => {
    // Arrange:设置state返回有userId的状态
    (storeMock.state as jest.Mock).mockReturnValue({ userId: '123' });

    // Act
    const canActivate = await TestBed.inject(PermissionGuard).canActivate(...);

    // Assert
    expect(canActivate).toBe(true);
    expect(storeMock.state).toHaveBeenCalled();
    expect(storeMock.isLoggedIn()).toBe(true);
  });
});

关键总结

  1. Signal本质是可调用函数,mock返回Signal的getter时,必须让getter返回一个mock函数
  2. 验证Signal是否被访问,等价于验证这个mock函数是否被调用(toHaveBeenCalled())
  3. 三种方案可根据测试场景选择:手动mock灵活性最高,结合auto-jest-spies保留原有spy功能,mock state更贴近真实业务逻辑

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.18 17:20:17