Angular单元测试中Mock/Spy辅助函数遇可写性报错问题
解决Angular单元测试中spyOn辅助函数的可写性报错问题
问题原因
通过import * as Helpers from 'helper-lib'导入的模块对象是只读的,Jasmine的spyOn需要修改对象属性来替换为间谍函数,但只读属性不允许被修改,因此抛出fn1 is not declared writable or has no setter错误。
解决方案
方案1:将辅助函数封装为Angular服务(推荐)
契合Angular依赖注入的设计思路,便于测试和长期维护:
- 重构辅助模块为Angular服务
// helper.service.ts import { Injectable } from '@angular/core'; @Injectable({ providedIn: 'root' }) export class HelperService { fn1(a: number, b: number): number { return a * b; } fn2(a: number, b: number): number { return a + b; } fn3(a: number, b: number): number { return a - b; } }
- 修改组件,通过依赖注入使用服务
// myComponent.ts import { Component } from '@angular/core'; import { HelperService } from 'helper-lib'; @Component({...}) export class MyComponent { constructor(private helperService: HelperService) {} public btnClick(p1: number, p2: number): number { let value = this.helperService.fn1(p1, p2); console.log(value); return value; } }
- 单元测试中spyOn服务方法
// myComponent.spec.ts import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; import { MyComponent } from './myComponent'; import { HelperService } from 'helper-lib'; describe('MyComponent', () => { let component: MyComponent; let fixture: ComponentFixture<MyComponent>; let helperService: jasmine.SpyObj<HelperService>; beforeEach(waitForAsync(() => { // 创建服务的间谍对象 const spy = jasmine.createSpyObj('HelperService', ['fn1']); TestBed.configureTestingModule({ declarations: [MyComponent], providers: [{ provide: HelperService, useValue: spy }] }).compileComponents(); })); beforeEach(() => { fixture = TestBed.createComponent(MyComponent); component = fixture.componentInstance; helperService = TestBed.inject(HelperService) as jasmine.SpyObj<HelperService>; fixture.detectChanges(); }); it('should calc value', () => { helperService.fn1.and.returnValue(2); // 模拟返回值 let value = component.btnClick(1, 2); expect(helperService.fn1).toHaveBeenCalledWith(1, 2); expect(value).toBe(2); }); });
方案2:修改模块导出方式,用可写对象包裹辅助函数
如果不想重构为服务,可调整模块导出结构,让函数位于可写对象中:
- 重构myHelpers.ts的导出
// myHelpers.ts export const Helpers = { fn1(a: number, b: number): number { return a * b; }, fn2(a: number, b: number): number { return a + b; }, fn3(a: number, b: number): number { return a - b; } };
- 组件保持原有导入方式,测试时直接spyOn
// myComponent.spec.ts import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; import { MyComponent } from './myComponent'; import { Helpers } from 'helper-lib'; describe('MyComponent', () => { let component: MyComponent; let fixture: ComponentFixture<MyComponent>; beforeEach(waitForAsync(() => { TestBed.configureTestingModule({declarations:[MyComponent]}).compileComponents(); })); beforeEach(() => { fixture = TestBed.createComponent(MyComponent); component = fixture.componentInstance; fixture.detectChanges(); }); it('should calc value', () => { const mySpy = spyOn(Helpers, 'fn1').and.returnValue(2); let value = component.btnClick(1, 2); expect(mySpy).toHaveBeenCalledWith(1, 2); expect(value).toBe(2); }); });
方案3:临时修改属性可写性(不推荐,hack方式)
若无法修改辅助模块结构,可在测试中临时将函数属性改为可写:
// myComponent.spec.ts it('should calc value', () => { // 临时将fn1改为可写 Object.defineProperty(Helpers, 'fn1', { writable: true }); const mySpy = spyOn(Helpers, 'fn1').and.returnValue(2); let value = component.btnClick(1, 2); expect(mySpy).toHaveBeenCalledWith(1, 2); expect(value).toBe(2); // 测试后恢复只读(可选) Object.defineProperty(Helpers, 'fn1', { writable: false }); });
内容的提问来源于stack exchange,提问作者Oam Psy
相关产品推荐
相关产品推荐

