在获得如何测试信号触发的更新的答案后(我的问题here),我在测试信号方面还有另一种情况。
我创建了一个 stackblitz 项目。
在旧文件夹中,您可以看到我的behaviorSubjects以及我如何测试它们。 在新文件夹中,我尝试迁移到信号。但变化检测似乎并没有按照我的预期工作。
当我使用计算信号时,信号会根据变化进行更新。但是,如果我存储的值实际上发生了变化,我只想更新我的信号,从而传播更改。所以我尝试在构造函数中执行类似于之前的操作。
constructor() {
const signalState = this.signalService.getValue();
if (signalState.foo !== this.usingSignalState()) {
this.usingSignalState.set(signalState.foo);
}
}
但是我似乎没有注册变更检测,因为测试是红色的。
我做错了什么?
首先要知道的是,信号有自己的 equals 函数,它确定信号何时发生变化。我正在使用
lodash
isEqual
函数来确定相等性(至少必须更改一个属性才能注册更改)。
在您的场景中,您只需检查
foo
属性并触发更改检测。
export class SignalService {
private signalState = signal<SignalState>({ foo: "", bar: undefined }, {
equal: this.isChanged // <- notice!
});
public getValue = this.signalState.asReadonly();
public updateFoo(foo: string) {
this.updateSignal({ foo });
}
private isChanged(currentState: SignalState, updatedState: SignalState) {
return isEqual(currentState, updatedState); // <- notice!
}
这消除了代码中的许多 if 条件。但接下来您需要设置的是一种测试信号上发生的变化的方法。为了实现这一目标,我使用继承主服务的测试服务。
这样做的目的是公开使用
toObservable
从信号创建的可观察量。该属性的要点是当信号发生变化时触发。
但是信号是一个
ReplaySubject
,所以它也会在初始订阅时触发,为了消除这个初始发射(来自重播主题),我正在使用skip(1)
,现在你可以检查信号变化,因为可观察的发射一个值.
@Injectable()
export class UsingSignalServiceTesting extends UsingSignalService {
signalChange$ = toObservable(this['signalService'].getValue).pipe(
skip(1),
);
}
最后,使用这种方法,我们可以发出一个值,但是内部发出信号
equal
,方法将过滤掉相同的值发射。
另请注意,我创建了一个在订阅时被调用的间谍,这用于检测信号上发生的更改。
由于我们使用异步订阅,因此您需要
fakeAsync
和 flush
来等待订阅完成。
it("should update state when signalState changes", fakeAsync(() => {
const signalService = spectator.inject(SignalService);
const spyFn = jest.fn();
service.signalChange$.subscribe(spyFn);
signalService["signalState"].set({ foo: "123", bar: true });
flush();
expect(spyFn).toHaveBeenCalled();
}));
import { Injectable, signal } from "@angular/core";
import { isEqual } from "lodash";
export interface SignalState {
foo: string;
bar: boolean | undefined;
}
@Injectable({
providedIn: "root",
})
export class SignalService {
private signalState = signal<SignalState>({ foo: "", bar: undefined }, {
equal: this.isChanged
});
public getValue = this.signalState.asReadonly();
public updateFoo(foo: string) {
this.updateSignal({ foo });
}
private isChanged(currentState: SignalState, updatedState: SignalState) {
return isEqual(currentState, updatedState);
}
private updateSignal(newState: Partial<SignalState>) {
const currentState = this.getValue();
const updatedState: SignalState = { ...currentState, ...newState };
this.signalState.set(updatedState);
}
}
import { computed, inject, Injectable, signal } from "@angular/core";
import { SignalService } from "./signal.service";
export interface SignalState {
foo: string;
bar: boolean | undefined;
}
@Injectable({
providedIn: "root",
})
export class UsingSignalService {
private signalService = inject(SignalService);
public computedSignal = computed(() => this.signalService.getValue().foo);
constructor() {
}
}
import { SpectatorService } from "@ngneat/spectator";
import { createServiceFactory } from "@ngneat/spectator/jest";
import { SignalService } from "./signal.service";
import { UsingSignalService } from "./using-signal.service";
import { Injectable, inject } from "@angular/core";
import {toObservable} from "@angular/core/rxjs-interop";
import { fakeAsync, flush } from "@angular/core/testing";
import { skip } from "rxjs";
@Injectable()
export class UsingSignalServiceTesting extends UsingSignalService {
signalChange$ = toObservable(this['signalService'].getValue).pipe(
skip(1),
);
}
describe("SignalStateService", () => {
let spectator: SpectatorService<UsingSignalServiceTesting>;
let service: UsingSignalServiceTesting;
const currentState = {
foo: "",
bar: false,
};
const createService = createServiceFactory({
service: UsingSignalServiceTesting,
});
beforeEach(() => {
spectator = createService();
service = spectator.inject(UsingSignalServiceTesting);
});
it("should update computed signal when state changes", () => {
const signalService = spectator.inject(SignalService);
signalService["signalState"].set({ foo: "123", bar: true });
expect(spectator.service.computedSignal()).toEqual("123");
});
it("should update state when signalState changes", fakeAsync(() => {
const signalService = spectator.inject(SignalService);
const spyFn = jest.fn();
service.signalChange$.subscribe(spyFn);
signalService["signalState"].set({ foo: "123", bar: true });
flush();
expect(spyFn).toHaveBeenCalled();
}));
it("should not update state when signalState is the same", fakeAsync(() => {
const signalService = spectator.inject(SignalService);
const spyFn = jest.fn();
service.signalChange$.subscribe(spyFn);
signalService["signalState"].set({ foo: "", bar: undefined });
flush();
expect(spyFn).not.toHaveBeenCalled();
}));
});