Angular - 为什么我的弹珠测试对包含用户权限的 BehaviorSubject 不起作用?

Angular - why doesn't my marbles test work for my BehaviorSubject that contains user permissions?

我正在用 Angular 8.

编写一个应用程序

我决定使用带有 BehaviorSubject 的 rxjs 存储来进行简单的状态管理。

基本上,我的代码是一个将存储、加载和更新用户权限的服务。有时这将来自 HTTP 调用后的服务器,有时该信息将来自本地存储。用户权限更改后,所有订阅者都会得到更新。

这是我的 StackBlitz:https://stackblitz.com/edit/rxjs-state-management-get-set 请点击 'add profile' 按钮查看实际效果。

这是我的设置了单元测试的 StackBlitz:https://stackblitz.com/edit/rxjs-state-management-get-set-xpptmr

我已经使用基本订阅者和完成技术在其中编写了一些单元测试,但我想更深入地挖掘。

我读到我们可以使用 'marbles' 来测试 Observable 随时间变化的值:https://rxjs-dev.firebaseapp.com/guide/testing/internal-marble-tests

我想使用弹珠来测试我代码中的 'load' 函数。

服务商店的代码片段:

@Injectable({
    providedIn: 'root'
})
export class PersonaService {

    private readonly data: BehaviorSubject<IPersonaData>;
    public readonly sharedData: Observable<IPersonaData>;

    constructor() {
        this.data = new BehaviorSubject<IPersonaData>(null);
        this.sharedData = this.data.asObservable() as Observable<IPersonaData>;
    }
   
    public load(): void {

        const storedPersonaData: IPersonaData = {
            currentPersonaIndex: null,
            personas: []
        };

        const storedCurrentIndex: string = localStorage.getItem(Constants.KEYS.defaultPersonaIndex) as string;

        if (storedCurrentIndex != null) {
            storedPersonaData.currentPersonaIndex = parseInt(storedCurrentIndex, Constants.RADIX.BASE_10);
        }

        const storedPersonas: string = localStorage.getItem(Constants.KEYS.personas) as string;

        if (storedPersonas != null) {
            storedPersonaData.personas = JSON.parse(storedPersonas) as IPersona[];
        }

        this.data.next(storedPersonaData);
    }

    public clear(): void {
        this.data.next(null);
    }

}

基本上,'load' 函数会检查本地存储中是否存储了任何数据,如果有,它将更新 BehaviorSubject 值。我想测试值如何随时间变化,例如在调用加载函数之前它是值 1 而不是在调用函数之后它是值 2。

03/07/2020 尝试弹珠测试----编辑:

这是我的弹珠测试代码片段:

  fit("clear", () =>
    testScheduler.run(({ expectObservable }) => {
      const newPersonaData: IPersonaData = {
        currentPersonaIndex: 1,
        personas: [...mockPersonas] //TODO: is this the best way to make a copy of it???
      };

      //put the data in the behaviour subject first
      localStorage.setItem(
        Constants.KEYS.defaultPersonaIndex,
        newPersonaData.currentPersonaIndex.toString()
      );
      localStorage.setItem(
        Constants.KEYS.personas,
        JSON.stringify(newPersonaData.personas)
      );
       
      //service.sharedData - initial value should be null
      service.load(); //service.sharedData - value should be newPersonaData

      service.clear(); //service.sharedData - value should be null

      expectObservable(service.sharedData).toBe("abc", {
        a: null,
        b: newPersonaData,
        c: null
      });
    }));

错误:

finished in 0.368sRan 1 of 9 specs - run all1 spec, 1 failure, randomized with seed 98448Spec List | FailuresSpec List | Failures
PersonaService > clear
Expected $.length = 1 to equal 3. Expected $[1] = undefined to equal Object({ frame: 1, notification: Notification({ kind: 'N', value: Object({ currentPersonaIndex: 1, personas: [ Object({ PersonaId: 154, Current: true, Description: 'Employee', SecuritySelectionId: 804356, DefaultSelectionId: 0, ProfileId: 17, ProfileName: '', IsSingleEmployee: true, DefaultEmpId: 714, EmpNo: '305', Level: -2, LevelDescription: 'Employee 1', AccessBand: 0, AuthBand: 0, PersonaIndex: 0, ExpiryDate: ' ', CoveringUserName: '', CoveringStartDate: ' ', CoveringEndDate: ' ', mobileaccess: true, canclock: true, mustsavelocation: true, canbookholiday: true, viewroster: true, canbookabsence: true, canviewbalancedetails: true, canviewactivity: true, canviewtimesheet: false, canviewtimesheetapproval: false, View_Shift_Swap: false, View_Flexi: true, View_Team_Calendar: false, Cancel_Absence: true, Delete_Absence: false, Mobile_Upload_Photo: true, Mobile_Upload_Absence_Photo: true, Receive_Notifications: false, CanViewRosterV2: false, AbsenceActions: [ Object({ .... Expected $[2] = undefined to equal Object({ frame: 2, notification: Notification({ kind: 'N', value: null, error: undefined, hasValue: true }) }).
Error: Expected $.length = 1 to equal 3. Expected $[1] = undefined to equal Object({ frame: 1, notification: Notification({ kind: 'N', value: Object({ currentPersonaIndex: 1, personas: [ Object({ PersonaId: 154, Current: true, Description: 'Employee', SecuritySelectionId: 804356, DefaultSelectionId: 0, ProfileId: 17, ProfileName: '', IsSingleEmployee: true, DefaultEmpId: 714, EmpNo: '305', Level: -2, LevelDescription: 'Employee 1', AccessBand: 0, AuthBand: 0, PersonaIndex: 0, ExpiryDate: ' ', CoveringUserName: '', CoveringStartDate: ' ', CoveringEndDate: ' ', mobileaccess: true, canclock: true, mustsavelocation: true, canbookholiday: true, viewroster: true, canbookabsence: true, canviewbalancedetails: true, canviewactivity: true, canviewtimesheet: false, canviewtimesheetapproval: false, View_Shift_Swap: false, View_Flexi: true, View_Team_Calendar: false, Cancel_Absence: true, Delete_Absence: false, Mobile_Upload_Photo: true, Mobile_Upload_Absence_Photo: true, Receive_Notifications: false, CanViewRosterV2: false, AbsenceActions: [ Object({ .... Expected $[2] = undefined to equal Object({ frame: 2, notification: Notification({ kind: 'N', value: null, error: undefined, hasValue: true }) }). at <Jasmine> at TestScheduler.deepEqual [as assertDeepEqual] (https://rxjs-state-management-get-set-xpptmr.stackblitz.io/~/src/testing/persona.service.spec.ts:8:20) at eval (https://rxjs-state-management-get-set-xpptmr.stackblitz.io/turbo_modules/rxjs@6.5.5/internal/testing/TestScheduler.js:133:23) at <Jasmine>

示例:https://stackblitz.com/edit/rxjs-state-management-get-set-xpptmr?embed=1&file=src/testing/persona.service.spec.ts

我们需要做的是使用 ReplaySubject 来捕获 Observable 拥有的所有值。 如果我们使用 BehaviourSubject,它只会给我们最终值,而不是过去的所有值。

示例:

it('2a clear - should clear the data from the BehaviorSubject - should be null, data, null', () => testScheduler.run(({ expectObservable }) => {

    const replaySubject$ = new ReplaySubject<IPersonaData>();
    service.sharedData.subscribe(replaySubject$);

    const newPersonaData: IPersonaData = {
        currentPersonaIndex: 1,
        personas: [...mockPersonas] //TODO: is this the best way to make a copy of it???
    };

    //put the data in the behaviour subject first
    appSettings.setString(Constants.KEYS.defaultPersonaIndex, newPersonaData.currentPersonaIndex.toString());
    appSettings.setString(Constants.KEYS.personas, JSON.stringify(newPersonaData.personas));

    service.load();
    service.clear();

    expectObservable(replaySubject$).toBe('(abc)', { a: null, b: newPersonaData, c: null });

}));