运行ng test时触发Firebase Query.where()传入undefined值报错如何解决
报错原因
- 单元测试环境未模拟
ActivatedRoute路由参数:组件ngOnInit生命周期中会读取路由参数id传入taskService.getTask()方法,测试环境下默认路由参数为空,params['id']为undefined,而getTask()内部调用Firestore的Query.where()方法时直接使用了该值,触发参数非法报错。 - 单元测试环境未模拟
AngularFireAuth登录状态:测试环境默认没有登录用户,fire.user.subscribe拿到的用户对象为null,后续auth.getUserData(user?.uid)传入的uid为undefined,getUserData内部同样调用Query.where()查询用户数据时传入了空值。 - 组件代码缺少空值防御判断:没有对路由参数、用户对象做非空校验就直接传入Firebase查询方法,生产环境也可能出现同类报错。
修复方案
第一步:修改单元测试文件 task-settings.component.spec.ts,模拟依赖项
不需要在单元测试中引入真实的Firebase模块,直接Mock所有外部依赖即可:
import { ComponentFixture, TestBed } from '@angular/core/testing'; import { FormBuilder, FormsModule, ReactiveFormsModule } from '@angular/forms'; import { RouterTestingModule } from '@angular/router/testing'; import { ActivatedRoute, Params } from '@angular/router'; import { AngularFireAuth } from '@angular/fire/auth'; import { of } from 'rxjs'; import { TaskSettingsComponent } from './task-settings.component'; import { AuthService } from 'src/app/services/auth.service'; import { TaskService } from 'src/app/services/task.service'; import { ToastService } from 'src/app/services/toast.service'; // Mock所有外部服务 class MockAuthService { getUserData = (uid: string) => of({ uid, name: '测试用户' }); } class MockTaskService { getTask = (id: string) => of({ taskId: id, title: '测试任务', description: '测试描述', deadline: new Date(), recipients: [] }); updateTask = () => Promise.resolve(); deleteTask = () => Promise.resolve(); } class MockToastService { publish = () => {}; } describe('TaskSettingsComponent', () => { let component: TaskSettingsComponent; let fixture: ComponentFixture<TaskSettingsComponent>; beforeEach(async () => { await TestBed.configureTestingModule({ declarations: [ TaskSettingsComponent ], imports: [ RouterTestingModule, ReactiveFormsModule, FormsModule ], providers: [ FormBuilder, { provide: AuthService, useClass: MockAuthService }, { provide: TaskService, useClass: MockTaskService }, { provide: ToastService, useClass: MockToastService }, // 模拟ActivatedRoute参数 { provide: ActivatedRoute, useValue: { params: of<Params>({ id: 'test-task-123' }) } }, // 模拟AngularFireAuth登录用户 { provide: AngularFireAuth, useValue: { user: of({ uid: 'test-user-123' }) } } ] }) .compileComponents(); }); beforeEach(() => { fixture = TestBed.createComponent(TaskSettingsComponent); component = fixture.componentInstance; fixture.detectChanges(); }); it('should create', () => { expect(component).toBeTruthy(); }); });
第二步:修改组件代码 task-settings.component.ts,增加空值防御
// 修改ngOnInit内的相关逻辑,增加非空判断 ngOnInit(): void { // 原有term计算逻辑保留... this.updateTaskForm = this.fb.group({ title: ['', Validators.required], description: ['', Validators.required], deadline: ['', Validators.required], }); this.fire.user.subscribe((user: any) => { // 增加用户非空判断 if (user?.uid) { this.userData = user; this.auth.getUserData(user.uid).subscribe((res) => { this.fsData = res; }); } }); this.route.params .pipe( switchMap((params: Params) => { console.log(params['id']); // 增加路由id非空判断 if (params['id']) { return this.taskService.getTask(params['id']); } // 没有id时返回空流 return of(null); }) ) .subscribe((res) => { // 结果非空才赋值 if (res) { this.updateTaskForm.controls.title.setValue(res.title); this.updateTaskForm.controls.description.setValue(res.description); this.updateTaskForm.controls.deadline.setValue(res.deadline); this.taskData = res; console.log(res); } }); this.taskScopeArray = []; }
修改后单元测试不会再调用真实Firebase服务,同时组件也具备了空值防御能力,不会再触发该类参数错误。
内容的提问来源于stack exchange,提问作者Tootsie
相关产品推荐
相关产品推荐

