首页
/ NgRx SignalStore 测试指南:从基础到高级实践

NgRx SignalStore 测试指南:从基础到高级实践

2025-05-28 07:01:44作者:咎岭娴Homer

前言

在 Angular 状态管理领域,NgRx 一直是最受欢迎的解决方案之一。随着 Angular 16 引入 Signals 特性,NgRx 团队推出了 SignalStore 这一全新状态管理方案,它充分利用了 Signals 的响应式特性。本文将全面介绍如何为 SignalStore 编写有效的测试用例,涵盖从基础到高级的各种测试场景。

SignalStore 测试基础

无依赖测试

对于不依赖外部服务的简单 SignalStore,我们可以直接创建实例进行测试:

import { signalStore, withState } from '@ngrx/signals';

const CounterStore = signalStore(
  withState({ count: 0 })
);

describe('CounterStore', () => {
  it('should initialize with count 0', () => {
    const store = new CounterStore();
    expect(store.count()).toBe(0);
  });
});

这种测试方式简单直接,适合验证 Store 的初始状态和基础功能。

使用 TestBed 测试

大多数情况下,SignalStore 会依赖其他服务,这时我们需要使用 Angular 的测试工具 TestBed:

import { TestBed } from '@angular/core/testing';
import { signalStore, withState, withMethods } from '@ngrx/signals';

const AuthStore = signalStore(
  withState({ user: null }),
  withMethods(({ $user }) => ({
    login: (user) => patchState($user, user)
  }))
);

describe('AuthStore', () => {
  let store: AuthStore;

  beforeEach(() => {
    TestBed.configureTestingModule({
      providers: [AuthStore]
    });
    store = TestBed.inject(AuthStore);
  });

  it('should update user on login', () => {
    const testUser = { name: 'Test User' };
    store.login(testUser);
    expect(store.user()).toEqual(testUser);
  });
});

依赖注入与模拟

模拟服务依赖

当 Store 依赖外部服务时,我们需要模拟这些服务:

import { inject } from '@angular/core';
import { HttpClient } from '@angular/common/http';

const DataStore = signalStore(
  withState({ data: null }),
  withMethods((store, http = inject(HttpClient)) => ({
    loadData: () => {
      http.get('/api/data').subscribe(data => {
        patchState(store, { data });
      });
    }
  }))
);

describe('DataStore', () => {
  let store: DataStore;
  let httpMock: jest.Mocked<HttpClient>;

  beforeEach(() => {
    httpMock = {
      get: jest.fn()
    } as any;

    TestBed.configureTestingModule({
      providers: [
        DataStore,
        { provide: HttpClient, useValue: httpMock }
      ]
    });
    
    store = TestBed.inject(DataStore);
  });

  it('should load data from API', () => {
    const testData = { id: 1 };
    httpMock.get.mockReturnValue(of(testData));
    
    store.loadData();
    
    expect(httpMock.get).toHaveBeenCalledWith('/api/data');
    expect(store.data()).toEqual(testData);
  });
});

测试 rxMethod

rxMethod 是 SignalStore 中处理异步操作的重要特性,测试时需要特别注意:

import { rxMethod } from '@ngrx/signals';
import { of } from 'rxjs';

const SearchStore = signalStore(
  withState({ results: [] }),
  withMethods((store) => ({
    search: rxMethod<string>((query$) => {
      return query$.pipe(
        switchMap(query => 
          query ? http.get(`/search?q=${query}`) : of([])
        ),
        tap(results => patchState(store, { results }))
      );
    }))
  })
);

describe('SearchStore', () => {
  let store: SearchStore;
  let httpMock: jest.Mocked<HttpClient>;

  beforeEach(() => {
    httpMock = {
      get: jest.fn()
    } as any;

    TestBed.configureTestingModule({
      providers: [
        SearchStore,
        { provide: HttpClient, useValue: httpMock }
      ]
    });
    
    store = TestBed.inject(SearchStore);
  });

  it('should perform search', () => {
    const testResults = [{ id: 1 }];
    httpMock.get.mockReturnValue(of(testResults));
    
    store.search('test');
    
    expect(httpMock.get).toHaveBeenCalledWith('/search?q=test');
    expect(store.results()).toEqual(testResults);
  });

  it('should handle empty query', () => {
    store.search('');
    expect(store.results()).toEqual([]);
    expect(httpMock.get).not.toHaveBeenCalled();
  });
});

高级测试技巧

测试自定义 Store 特性

对于复杂的自定义 Store 特性,我们可以单独测试其行为:

function withLogger() {
  return (store: SignalStore) => {
    const actions = new Subject<string>();
    
    return {
      ...store,
      logAction: (action: string) => actions.next(action),
      actions$: actions.asObservable()
    };
  };
}

describe('withLogger', () => {
  it('should log actions', () => {
    const TestStore = signalStore(withLogger());
    const store = new TestStore();
    
    const spy = jest.fn();
    store.actions$.subscribe(spy);
    
    store.logAction('test');
    
    expect(spy).toHaveBeenCalledWith('test');
  });
});

测试计算属性

计算属性(computed)是 SignalStore 的重要特性,测试时需要注意其惰性求值特性:

const CartStore = signalStore(
  withState({ items: [] }),
  withComputed(({ items }) => ({
    total: computed(() => 
      items().reduce((sum, item) => sum + item.price, 0)
    )
  }))
);

describe('CartStore', () => {
  it('should calculate total', () => {
    const store = new CartStore();
    expect(store.total()).toBe(0);
    
    patchState(store, { 
      items: [{ price: 10 }, { price: 20 }] 
    });
    
    // 必须访问计算属性才会触发计算
    expect(store.total()).toBe(30);
  });
});

测试最佳实践

  1. 隔离测试:尽量将业务逻辑与状态管理分离,使得 Store 主要处理状态变更,业务逻辑由服务处理。

  2. 小范围测试:每个测试用例只验证一个特定行为,保持测试简洁明确。

  3. 状态验证:测试 Store 时,重点验证状态变更是否符合预期,而不是实现细节。

  4. 异步处理:对于涉及异步操作的测试,确保使用适当的工具(如 fakeAsync 或 waitForAsync)处理异步行为。

  5. 类型安全:充分利用 TypeScript 的类型系统,确保测试代码也能享受类型检查的好处。

结语

SignalStore 作为 NgRx 的新成员,为 Angular 应用状态管理带来了更简洁、更响应式的解决方案。通过本文介绍的各种测试方法,开发者可以确保 SignalStore 在各种场景下都能可靠工作。随着 SignalStore 的不断演进,测试方法也将持续完善,建议开发者关注 NgRx 官方文档获取最新测试实践。

登录后查看全文
热门项目推荐

热门内容推荐

最新内容推荐

项目优选

收起
docsdocs
OpenHarmony documentation | OpenHarmony开发者文档
Dockerfile
144
1.93 K
kernelkernel
deepin linux kernel
C
22
6
ohos_react_nativeohos_react_native
React Native鸿蒙化仓库
C++
192
274
openGauss-serveropenGauss-server
openGauss kernel ~ openGauss is an open source relational database management system
C++
145
189
RuoYi-Vue3RuoYi-Vue3
🎉 (RuoYi)官方仓库 基于SpringBoot,Spring Security,JWT,Vue3 & Vite、Element Plus 的前后端分离权限管理系统
Vue
930
553
nop-entropynop-entropy
Nop Platform 2.0是基于可逆计算理论实现的采用面向语言编程范式的新一代低代码开发平台,包含基于全新原理从零开始研发的GraphQL引擎、ORM引擎、工作流引擎、报表引擎、规则引擎、批处理引引擎等完整设计。nop-entropy是它的后端部分,采用java语言实现,可选择集成Spring框架或者Quarkus框架。中小企业可以免费商用
Java
8
0
openHiTLSopenHiTLS
旨在打造算法先进、性能卓越、高效敏捷、安全可靠的密码套件,通过轻量级、可剪裁的软件技术架构满足各行业不同场景的多样化要求,让密码技术应用更简单,同时探索后量子等先进算法创新实践,构建密码前沿技术底座!
C
423
392
金融AI编程实战金融AI编程实战
为非计算机科班出身 (例如财经类高校金融学院) 同学量身定制,新手友好,让学生以亲身实践开源开发的方式,学会使用计算机自动化自己的科研/创新工作。案例以量化投资为主线,涉及 Bash、Python、SQL、BI、AI 等全技术栈,培养面向未来的数智化人才 (如数据工程师、数据分析师、数据科学家、数据决策者、量化投资人)。
Jupyter Notebook
75
66
CangjieCommunityCangjieCommunity
为仓颉编程语言开发者打造活跃、开放、高质量的社区环境
Markdown
1.11 K
0
openHiTLS-examplesopenHiTLS-examples
本仓将为广大高校开发者提供开源实践和创新开发平台,收集和展示openHiTLS示例代码及创新应用,欢迎大家投稿,让全世界看到您的精巧密码实现设计,也让更多人通过您的优秀成果,理解、喜爱上密码技术。
C
64
511