如何测试使用Redux的Angular(5) SPA组件(单元测试)?
嘿,我来帮你搞定Angular 5结合Redux的服务单元测试!针对你提到的getTodos()方法,咱们先把完整的服务代码补全(你之前的代码没写完,我补充了常见的成功/错误处理逻辑),再一步步拆解怎么写靠谱的单元测试。
先补全todo.service.ts的完整代码
import { Injectable } from '@angular/core'; import { HttpClient } from '@angular/common/http'; import { Observable } from 'rxjs'; import { NgRedux } from '@angular-redux/store'; import { SHOW_LOADER, HIDE_LOADER, SET_TODOS, FETCH_TODOS_ERROR } from './action-types'; @Injectable() export class TodoService { constructor(private http: HttpClient, private ngRedux: NgRedux<any>) {} getTodos(): Observable<any> { this.ngRedux.dispatch({ type: SHOW_LOADER, message: "Loading TODOs ..." }); let obs = this.http.get<Array<any>>("api/SampleData/GetTodoList"); obs.subscribe( result => { console.log("Got todos:", result); this.ngRedux.dispatch({ type: SET_TODOS, payload: result }); this.ngRedux.dispatch({ type: HIDE_LOADER }); }, error => { console.error("Failed to fetch todos:", error); this.ngRedux.dispatch({ type: FETCH_TODOS_ERROR, payload: error.message }); this.ngRedux.dispatch({ type: HIDE_LOADER }); } ); return obs; } }
单元测试核心思路
我们需要验证getTodos()的几个关键行为:
- 调用时是否正确触发
SHOW_LOADERaction - 是否发起了正确的HTTP GET请求
- 请求成功时,是否依次触发
SET_TODOS和HIDE_LOADER - 请求失败时,是否触发
FETCH_TODOS_ERROR和HIDE_LOADER - 是否正确返回HTTP请求的Observable
完整的测试文件todo.service.spec.ts
import { TestBed } from '@angular/core/testing'; import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing'; import { NgRedux } from '@angular-redux/store'; import { TodoService } from './todo.service'; import { SHOW_LOADER, HIDE_LOADER, SET_TODOS, FETCH_TODOS_ERROR } from './action-types'; // 手动Mock NgRedux,避免依赖真实Store class MockNgRedux { dispatch = jasmine.createSpy('dispatch'); } describe('TodoService', () => { let service: TodoService; let httpMock: HttpTestingController; let ngReduxMock: MockNgRedux; beforeEach(() => { TestBed.configureTestingModule({ imports: [HttpClientTestingModule], providers: [ TodoService, { provide: NgRedux, useClass: MockNgRedux } ] }); service = TestBed.inject(TodoService); httpMock = TestBed.inject(HttpTestingController); ngReduxMock = TestBed.inject(NgRedux) as unknown as MockNgRedux; }); afterEach(() => { // 确保所有HTTP请求都被处理,防止内存泄漏 httpMock.verify(); // 重置dispatch的调用记录,保证测试间互不干扰 ngReduxMock.dispatch.calls.reset(); }); it('should be created', () => { expect(service).toBeTruthy(); }); it('should dispatch SHOW_LOADER and send GET request on getTodos() call', () => { // 调用目标方法 service.getTodos(); // 验证SHOW_LOADER是否被正确触发 expect(ngReduxMock.dispatch).toHaveBeenCalledWith({ type: SHOW_LOADER, message: "Loading TODOs ..." }); // 捕获HTTP请求,验证URL和请求方法 const req = httpMock.expectOne('api/SampleData/GetTodoList'); expect(req.request.method).toBe('GET'); }); it('should dispatch SET_TODOS and HIDE_LOADER when request succeeds', () => { const mockTodos = [{ id: 1, title: 'Finish unit test' }, { id: 2, title: 'Review code' }]; // 调用方法并订阅结果 service.getTodos().subscribe(todos => { expect(todos).toEqual(mockTodos); }); // 模拟后端成功响应 const req = httpMock.expectOne('api/SampleData/GetTodoList'); req.flush(mockTodos); // 验证actions的触发顺序和内容 expect(ngReduxMock.dispatch).toHaveBeenCalledTimes(3); expect(ngReduxMock.dispatch.calls.argsFor(0)).toEqual([{ type: SHOW_LOADER, message: "Loading TODOs ..." }]); expect(ngReduxMock.dispatch.calls.argsFor(1)).toEqual([{ type: SET_TODOS, payload: mockTodos }]); expect(ngReduxMock.dispatch.calls.argsFor(2)).toEqual([{ type: HIDE_LOADER }]); }); it('should dispatch FETCH_TODOS_ERROR and HIDE_LOADER when request fails', () => { const mockErrorMsg = 'Internal Server Error'; // 调用方法并订阅错误回调 service.getTodos().subscribe({ error: err => { expect(err.error).toBe(mockErrorMsg); expect(err.status).toBe(500); } }); // 模拟后端错误响应 const req = httpMock.expectOne('api/SampleData/GetTodoList'); req.flush(mockErrorMsg, { status: 500, statusText: 'Server Error' }); // 验证错误场景下的actions expect(ngReduxMock.dispatch).toHaveBeenCalledTimes(3); expect(ngReduxMock.dispatch.calls.argsFor(0)).toEqual([{ type: SHOW_LOADER, message: "Loading TODOs ..." }]); expect(ngReduxMock.dispatch.calls.argsFor(1)).toEqual([{ type: FETCH_TODOS_ERROR, payload: mockErrorMsg }]); expect(ngReduxMock.dispatch.calls.argsFor(2)).toEqual([{ type: HIDE_LOADER }]); }); });
关键测试细节说明
- Mock NgRedux:我们用自定义的
MockNgRedux类替代真实的Redux Store,用Jasmine的spy跟踪dispatch方法的调用,完全隔离Redux依赖。 - HttpClientTestingModule:Angular官方提供的测试模块,用来模拟HTTP请求,避免实际调用后端API,保证测试的独立性。
- 测试覆盖:覆盖了服务初始化、正常请求、错误请求三个核心场景,确保所有关键逻辑都被验证。
内容的提问来源于stack exchange,提问作者Alexei - check Codidact
相关产品推荐
相关产品推荐

