Angular使用Jasmine&Karma编写路由跳转、Material弹窗点击事件单元测试
Angular Jasmine & Karma 单元测试问题解答
1. 测试调用router.navigateByUrl的点击事件
直接用Mock的Router服务做测试即可,不需要引入真实路由模块,测试逻辑如下:
- 用
jasmine.createSpyObj创建带navigateByUrl间谍方法的Router mock对象 - 在测试模块的providers中替换真实Router为mock对象
- 触发
onClickCancel方法后断言间谍方法调用符合预期
完整测试代码示例:
import { TestBed } from '@angular/core/testing'; import { Router } from '@angular/router'; import { YourComponent } from './your.component'; // 创建Router mock对象 const mockRouter = jasmine.createSpyObj('Router', ['navigateByUrl']); describe('YourComponent', () => { let component: YourComponent; let router: Router; beforeEach(() => { TestBed.configureTestingModule({ declarations: [YourComponent], providers: [ { provide: Router, useValue: mockRouter } ] }); component = TestBed.createComponent(YourComponent).componentInstance; router = TestBed.inject(Router); }); it('should navigate to login page when onClickCancel is called', () => { component.onClickCancel(); // 断言navigateByUrl被调用1次,且入参为'login' expect(router.navigateByUrl).toHaveBeenCalledOnceWith('login'); }); });
2. 测试Angular Material Dialog调用及报错解决
报错原因说明
TypeError: Cannot read property 'count' of undefined 错误的核心原因是dialog.open没有被正确封装为Jasmine间谍对象,只有经过spyOn或者jasmine.createSpy创建的方法才会携带calls属性。
完整测试实现
需要同时mock MatDialog和DialogRef对象,保证afterClosed方法返回可观察对象,测试代码如下:
import { TestBed, fakeAsync, tick } from '@angular/core/testing'; import { MatDialog } from '@angular/material/dialog'; import { of } from 'rxjs'; import { YourComponent } from './your.component'; import { AddNewRoleComponent } from './add-new-role.component'; // mock DialogRef对象,提供afterClosed方法 const mockDialogRef = { afterClosed: () => of(null) }; // 创建带open方法间谍的MatDialog mock对象 const mockDialog = jasmine.createSpyObj('MatDialog', ['open']); describe('YourComponent', () => { let component: YourComponent; let dialog: MatDialog; beforeEach(() => { // 每次测试前重置间谍调用记录,避免用例间相互干扰 mockDialog.open.calls.reset(); mockDialog.open.and.returnValue(mockDialogRef); // 为getRoles创建间谍,方便后续断言 spyOn(component, 'getRoles'); TestBed.configureTestingModule({ declarations: [YourComponent], providers: [ { provide: MatDialog, useValue: mockDialog } ] }); component = TestBed.createComponent(YourComponent).componentInstance; dialog = TestBed.inject(MatDialog); }); // 测试addNewRole方法 it('should open AddNewRoleComponent with correct config when addNewRole is called', () => { component.addNewRole(); expect(dialog.open).toHaveBeenCalledOnceWith(AddNewRoleComponent, { width: '450px', disableClose: true }); // 也可以用calls.count()写法断言调用次数 // expect(dialog.open.calls.count()).toBe(1); }); // 测试editRole方法 it('should open AddNewRoleComponent with role data when editRole is called', () => { const testRole = { id: 1, name: '测试角色' }; component.editRole(testRole); expect(dialog.open).toHaveBeenCalledOnceWith(AddNewRoleComponent, { width: '450px', disableClose: true, data: testRole }); }); // 测试弹窗关闭后触发getRoles it('should call getRoles after dialog closed', fakeAsync(() => { component.addNewRole(); tick(); // 触发afterClosed的Observable订阅逻辑 expect(component.getRoles).toHaveBeenCalled(); })); });
内容的提问来源于stack exchange,提问作者Deepak
相关产品推荐
相关产品推荐

