如何使用Jasmine和Karma编写Angular单元测试覆盖getNavList全代码行
核心问题诊断
你之前覆盖率低的核心原因大概率是只测了方法调用的返回值,没有覆盖到方法内部的所有分支、异步逻辑、异常场景、以及组件和服务的交互逻辑,包括依赖注入的mock有效性、订阅回调、错误处理、边界条件都没覆盖到。
前置测试配置调整
首先保证测试配置中正确mock NavService,不要用真实服务,避免外部依赖影响测试稳定性:
// 组件spec.ts开头的测试模块配置 import { ComponentFixture, TestBed } from '@angular/core/testing'; import { YourComponent } from './your.component'; import { NavService } from './services/nav.service'; // mock NavService const mockNavService = jasmine.createSpyObj('NavService', ['getNavList']); describe('YourComponent', () => { let component: YourComponent; let fixture: ComponentFixture<YourComponent>; // 暴露mock服务的引用,方便用例中修改返回值 let navServiceSpy: jasmine.SpyObj<NavService>; beforeEach(async () => { await TestBed.configureTestingModule({ declarations: [YourComponent], providers: [ { provide: NavService, useValue: mockNavService } ] }).compileComponents(); navServiceSpy = TestBed.inject(NavService) as jasmine.SpyObj<NavService>; fixture = TestBed.createComponent(YourComponent); component = fixture.detectChanges(); }); });
全覆盖测试用例实现
需要覆盖以下所有场景,保证每行代码都被命中:
1. 正常返回数据场景
覆盖服务调用成功、组件处理返回值的逻辑分支:
it('调用getNavList成功时应正确处理返回的导航列表', () => { // 构造模拟返回数据,覆盖所有返回字段的边界值,比如空数组、带权限字段的项等 const mockNavList = [ { id: 1, name: '首页', path: '/home', permission: 'common' }, { id: 2, name: '管理页', path: '/admin', permission: 'admin' } ]; navServiceSpy.getNavList.and.returnValue(of(mockNavList)); // 触发组件中调用服务的逻辑,比如组件初始化、点击事件触发等 component.loadNavList(); fixture.detectChanges(); // 断言服务被正确调用 expect(navServiceSpy.getNavList).toHaveBeenCalledTimes(1); // 断言组件处理后的数据符合预期,比如组件存储的navList变量、渲染的DOM节点 expect(component.navList).toEqual(mockNavList); // 如果有DOM渲染逻辑,补充DOM断言覆盖模板代码 const navItems = fixture.nativeElement.querySelectorAll('.nav-item'); expect(navItems.length).toBe(2); });
2. 空返回值场景
覆盖服务返回空数组/空值的逻辑分支,避免空指针异常的分支漏测:
it('getNavList返回空数组时应正常处理无导航的情况', () => { navServiceSpy.getNavList.and.returnValue(of([])); component.loadNavList(); fixture.detectChanges(); expect(component.navList.length).toBe(0); // 如果有空状态提示,断言空状态DOM存在 const emptyTip = fixture.nativeElement.querySelector('.empty-nav-tip'); expect(emptyTip).toBeTruthy(); });
3. 异常报错场景
覆盖服务调用失败、错误处理的逻辑分支,这是很多人漏测导致覆盖率低的核心点:
it('getNavList调用失败时应执行错误处理逻辑', () => { // mock服务返回错误 const mockError = new Error('获取导航列表失败'); navServiceSpy.getNavList.and.returnValue(throwError(() => mockError)); // 如果你组件中有错误提示的变量,比如errorMsg,要做断言 spyOn(component, 'handleError'); // 如果有单独的错误处理方法,监听调用 component.loadNavList(); fixture.detectChanges(); expect(navServiceSpy.getNavList).toHaveBeenCalledTimes(1); expect(component.handleError).toHaveBeenCalledWith(mockError); expect(component.errorMsg).toBe('导航加载失败,请稍后重试'); });
4. 权限过滤场景
如果getNavList返回后组件有权限过滤逻辑,单独覆盖权限分支:
it('应根据当前用户权限过滤导航列表', () => { // mock当前用户是普通用户,没有admin权限 component.currentUserRole = 'common'; const mockNavList = [ { id: 1, name: '首页', path: '/home', permission: 'common' }, { id: 2, name: '管理页', path: '/admin', permission: 'admin' } ]; navServiceSpy.getNavList.and.returnValue(of(mockNavList)); component.loadNavList(); fixture.detectChanges(); // 断言过滤后只有1条导航 expect(component.navList.length).toBe(1); expect(component.navList[0].name).toBe('首页'); });
Sonar覆盖率达标注意事项
- 所有的
if/else分支都要单独写用例覆盖,包括三元表达式的两个结果 - 异步逻辑的订阅回调、
pipe里的操作符(比如map、catchError)都要覆盖到 - 模板中的
*ngIf、*ngFor逻辑也要通过DOM断言覆盖,否则Sonar会统计模板代码的覆盖率缺失 - 测试配置中开启全量覆盖率统计,
karma.conf.js中配置coverageReporter包含所有ts和html文件
内容的提问来源于stack exchange,提问作者Deepak
相关产品推荐
相关产品推荐

