Angular - 使用 Pairwise 运算符测试 Observable

Angular - testing Observable with Pairwise operator

我正在使用 Angular 并且一直在使用 jasmine-marbles 编写单元测试。这是一个我似乎无法弄清楚的场景。我将如何测试 determineNextSteps$?除了使用 jasmine-marbles 之外,我也愿意接受其他测试解决方案。

示例:

export class Service {
  private person$: Observable<any> = this.store.pipe(
    select(fromStore.selectPersonState),
    map(({ person }) => person)
  );

  private parentSetsState$: Observable<any> = this.store.pipe(
    select(fromStore.selectParentState)
  );

  private childSet$: Observable<any> = this.parentSetsState$.pipe(
    map(({ parentSets }) =>
      parentSets.find(set => set.name === 'FooBar')
    )
  );

  determineNextSteps$: Observable<any> = this.childSet$.pipe(
    tap(set =>
      this.store.dispatch(
        fromStore.determineStep({
          set: set
        })
      )
    ),
    pairwise(),
    concatMap(pairedSet =>
      this.person$.pipe(
        first(),
        map(person =>
          !pairedSet[0].complete && pairedSet[1].complete
            ? this.router.navigate(['/home', 'profile', person.id, 'next-steps'])
            : pairedSet
        )
      )
    )
  );

  constructor(private store: Store<any>, private router: Router) {
  }
}

当前尝试:

interface StoreState extends MockStoreState {}
describe('Service', () => {
  let service: Service;
  let mockStore: MockStore<fromStore.State>;
  let router: Router;
  let mockPersonStateSelector: MemoizedSelector<fromStore.State, fromPersonReducer.PersonState>;
  let mockRegistrationSetsStateSelector: MemoizedSelector<
    fromStore.State,
    fromSetsReducer.SetsState
  >;
  const initialState: StoreState = { ...mockStoreInitialState };
  beforeEach(() => {
    TestBed.configureTestingModule({
      providers: [
        Service,
        { provide: Router, useValue: { navigate: jest.fn() } },
        provideMockStore({ initialState })
      ]
    });
    mockStore = TestBed.get(Store);
    router = TestBed.get(Router);
    mockPersonStateSelector = mockStore.overrideSelector(fromStore.selectPersonState, {
      ...fromPersonReducer.initialState
    });
    mockSetsStateSelector = mockStore.overrideSelector(fromProfileStore.selectSetsState, {
      ...fromSetsReducer.initialState
    });
    service = TestBed.get(Service);
    spyOn(mockStore, 'dispatch');
  });

  it('should be created', () => {
    expect(service).toBeTruthy();
  });

  describe('determineNextSteps$', () => {
    describe('when the set completes after a previous incompletion', () => {
      it('navigates the user to their next-steps', () => {
        mockPersonStateSelector.setResult({ ...fromPersonReducer.initialState, person: { id: '1' } as any });
        service['childSet$'] = cold('(ab)', {
          a: { name: 'foobar', complete: false },
          b: { name: 'foobar', complete: true }
        });
        expect(service.determineNextSteps$).toBeObservable(cold(''));
        expect(router.navigate).toHaveBeenCalledWith(['/home', 'profile', '1', 'next-steps']);
      });
    });
  });
});

这是我的做法:

const testScheduler = new TestScheduler(assertionFunction);

testScheduler.run(({ cold, expectObservable }) => {
  const p1 = { name: 'FooBar', id: 1, complete: true };
  const p2 = { name: 'FooBar', id: 4, complete: true };

  const eventValues = { 
    a: [p1, { name: 'smthElse', id:2 }],
    b: [{ name: 'baz', id: 3 }, p2],
  };
  const events$ = cold('---a----b|', eventValues);
  const expected = '    --------v';

  const src$ = merge(
    events$.pipe(
      tap((parentSets) => mockStore.setState({ parentSets })),
      // We just want to subscribe to it, but not interested in any elements
      ignoreElements(),
    ),
    // This is the source we're interested in
    service.determineNextSteps$
  );

  // Used in `determineNextSteps$`'s `concatMap`
  mockStore.overrideSelector(fromStore.selectPersonState, { person: personObj });

  expectObservable(src$).toBe(expected, { v: true });
})

然后您也可以使用相同的模式来测试其他情况。