如何只在某个 Angular 单元测试中更新 signal 值?
每个测试都应创建独立的 WritableSignal,让服务和组件引用同一个 signal,并在需要时通过 .set() 修改它的值。断言时要调用 signal:component.mySignal().extraProperty。
为什么替换 getter 没有效果?
组件中的这行代码在初始化时读取服务属性,并保存返回的 signal 引用:
mySignal: Signal<any> = this.myService.mySignal;
组件创建后,即使让服务 getter 返回另一个 signal,也不会改变组件已经保存的引用。fixture.detectChanges() 同样不会重新执行这条字段初始化语句。
原来的 getter 每次执行时还会创建一个新的 signal:
get: () => signal(mockSignalValue)
因此,不同的读取者可能拿到不同的实例。测试时应先创建 signal,再让服务始终返回这个实例。
为每个测试创建可修改的 signal
下面沿用原例中把 MyComponent 放入 imports 的方式,前提是它是 standalone 组件。示例假设组件只需要服务的 mySignal;如果组件还访问其他成员,也需要在 mock 中提供。
import { signal, WritableSignal } from '@angular/core';
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { MyComponent } from './my-component';
import { MyService } from './my-service';
interface SignalValue {
someProperty: string;
extraProperty?: string;
}
describe('MyComponent', () => {
let component: MyComponent;
let fixture: ComponentFixture<MyComponent>;
let testSignal: WritableSignal<SignalValue>;
beforeEach(async () => {
testSignal = signal<SignalValue>({ someProperty: '' });
const myServiceMock = {
mySignal: testSignal,
};
await TestBed.configureTestingModule({
imports: [MyComponent],
providers: [
{ provide: MyService, useValue: myServiceMock },
],
}).compileComponents();
fixture = TestBed.createComponent(MyComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should not have the extra property', () => {
expect(component.mySignal().extraProperty).toBeUndefined();
});
it('should have the extra property', () => {
testSignal.set({
someProperty: '',
extraProperty: '',
});
fixture.detectChanges();
expect(component.mySignal().extraProperty).toBe('');
});
});
beforeEach 每次都会创建新的 signal 和初始对象,所以第二个测试中的 .set() 不会影响其他测试。组件可以将它声明为只读的 Signal,测试仍可通过保留的 WritableSignal 引用修改其值。
如果需要保留 createSpyFromClass,也可以将服务属性配置为返回同一个实例:
Object.defineProperty(myServiceSpy, 'mySignal', {
get: () => testSignal,
configurable: true,
});
这段配置应放在组件创建之前,之后通过 testSignal.set(...) 修改值即可。
修正组件中的服务注入
原例中的 constructor(myService: MyService) {} 只声明了构造函数参数,没有创建 this.myService 成员。可以在构造函数中保存 signal:
export class MyComponent {
mySignal: Signal<any>;
constructor(myService: MyService) {
this.mySignal = myService.mySignal;
}
}
Signal 类型应从 @angular/core 导入。实际项目中可以用业务类型替换 any。
初始化时机与断言注意事项
- 如果只检查 signal 的当前值,
.set()后可以直接读取;如果检查模板更新,再调用fixture.detectChanges()。 - 如果测试构造函数或
ngOnInit根据初始值执行的逻辑,应在TestBed.createComponent()之前设置所需值。可以将组件创建移到各个测试中,并在第一次fixture.detectChanges()前完成设置。 extraProperty的值是空字符串'',不能用真假值判断它是否存在。若要严格检查对象是否拥有该属性,可以使用下面的断言:
expect(
Object.prototype.hasOwnProperty.call(
component.mySignal(),
'extraProperty',
),
).toBe(true);
原始配置还有两处语法问题:imports: [MyComponent]; 后应使用逗号,compileComponents 应写成方法调用 .compileComponents()。测试末尾也需要补齐 });。
备注:内容仅供参考。
