Как выполнить модульное тестирование функции `mergeMap ()` в ngrx / Effects, используя ReplaySubject ()? - PullRequest
0 голосов
/ 03 июня 2019

Я пишу юнит-тест для Angular ngrx / эффектов, для приложения todo. Я использую ReplaySubject (), поскольку они более интуитивно понятны и просты в тестировании, а не жасминовые метки (горячие и холодные).

Но я получаю следующую ошибку.

    should dispatch success and error actions for AddTodoItem
        HeadlessChrome 74.0.3729 (Linux 0.0.0)
        Error: Expected object to be a kind of AddTodoItemSuccess, but was LoadTodos({ type: '[Todo] Load Todos' }).
            at <Jasmine>
            at SafeSubscriber._next (src/app/store/effects/app.effects.spec.ts:46:24)
            at SafeSubscriber.__tryOrUnsub (node_modules/rxjs/_esm2015/internal/Subscriber.js:183:1)
            at SafeSubscriber.next (node_modules/rxjs/_esm2015/internal/Subscriber.js:122:1)

App.effects.ts

@Effect()
  loadTodos$ = this.actions$.pipe(
    ofType<fromTodos.LoadTodos>(TodoActionTypes.LOAD_TODOS),
    switchMap((action) => {
      return this.todoService.getTodos().pipe(
        map(data => {
          return new fromTodos.LoadTodosSuccess(data);
        }),
        catchError(err => of(new fromTodos.LoadTodosFailure(err)))
      );
    })
  );

  @Effect()
  addTodo$: Observable<Action> = this.actions$.pipe(
    ofType<fromTodos.AddTodoItem>(TodoActionTypes.ADD_TODO_ITEM),
    switchMap(action => {
      return this.todoService.addTodo(action.payload).pipe(
        mergeMap(data => {
          return [new fromTodos.AddTodoItemSuccess(data),  new fromTodos.LoadTodos()];
        }),
        catchError(err => of(new fromTodos.AddTodoItemFailure(err)))
      );
    })
  );

app.effects.spec.ts

describe('AppEffects', () => {
  let actions$: ReplaySubject<any>;
  let effects: AppEffects;
  const testTodo: Todo = {
    id: 0,
    todo: 'string',
    mark_as_done: true,
  };

  beforeEach(() => {
    TestBed.configureTestingModule({
      providers: [
        AppEffects,
        provideMockActions(() => actions$)
      ]
    });

    effects = TestBed.get(AppEffects);
  });

  // passsing
  it('should dispatch success and error actions for LoadTodos', () => {
    actions$ = new ReplaySubject(1);
    actions$.next(new fromActions.LoadTodos());

    effects.loadTodos$.subscribe(
      result => expect(result).toEqual(new fromActions.LoadTodosSuccess(null), 'should dispatch'),
      err => expect(err).toEqual(new fromActions.LoadTodosFailure(null))
    );
  });

  // failing
  it('should dispatch success and error actions for AddTodoItem', () => {
    actions$ = new ReplaySubject(1);
    actions$.next(new fromActions.AddTodoItem(testTodo));

    effects.addTodo$.subscribe(
      result => { console.log('AddTodoItem', result);
        expect(result).toEqual(new fromActions.AddTodoItemSuccess(undefined), new fromActions.LoadTodos());
      },
      err => expect(err).toEqual(new fromActions.AddTodoItemFailure(err))
    );
  });
});

Скриншот ошибки

error screenshot


Я ссылался на документацию ngrx, но у нее не было примера mergeMap. Как написать тест для эффектов, когда отправляются несколько действий, используя mergeMap ?

Ответы [ 3 ]

1 голос
/ 03 июня 2019

Ваш эффект возвращает два действия new fromTodos.AddTodoItemSuccess(data), new fromTodos.LoadTodos().

В своем тесте вы используете expect(result).toEqual(new fromActions.LoadTodos(), new fromActions.AddTodoItemSuccess(undefined));, который проверяет, является ли возвращенное действие LoadTodos. Поскольку второе возвращенное действие - это успешное действие, мы по-прежнему проверяем, является ли действие действием LoadTodos, что приводит к ошибке.

0 голосов
/ 04 июня 2019

Наконец разобрался, спасибо @timdeschryver за подсказки.

Вам нужно take и skip операторы rxjs, чтобы проверить, отправляются ли первое и второе действия, как ожидалось.

  • take (2) принимает только первые 2 события наблюдаемого
  • skip (2) пропускает первые 2 события наблюдаемых

app.effects.ts

  @Effect()
  addTodo$: Observable<Action> = this.actions$.pipe(
    ofType<fromTodos.AddTodoItem>(TodoActionTypes.ADD_TODO_ITEM),
    switchMap(action => {
      return this.todoService.addTodo(action.payload).pipe(
        mergeMap(data => {
          return [new fromTodos.AddTodoItemSuccess(data), new fromTodos.LoadTodos()];
        }),
        catchError(err => of(new fromTodos.AddTodoItemFailure(err)))
      );
    })
  );

app.effets.spec.ts

  it('should dispatch success and error actions for AddTodoItem', () => {
    actions$ = new ReplaySubject(1);
    actions$.next(new fromActions.AddTodoItem(testTodo));

    effects.addTodo$
          .pipe(take(1))  // this takes only the first event of the observable
          .subscribe(
            result => expect(result).toEqual(new fromActions.AddTodoItemSuccess(undefined), 'first action should be AddTodoItemSuccess'),
            err => expect(err).toEqual(new fromActions.AddTodoItemFailure(err))
          );

    effects.addTodo$
          .pipe(skip(1))  // this skips the first event of observable, and takes from second event (i.e. the second action alone will be available now)
          .subscribe(
            result => expect(result).toEqual(new fromActions.LoadTodos(), 'second action should be LoadTodos'),
            err => expect(err).toEqual(new fromActions.AddTodoItemFailure(err))
          );
  });
0 голосов
/ 03 июня 2019

Это порядок возвращаемого значения в loadTodo$ эффекте:

[new fromTodos.AddTodoItemSuccess(data), new fromTodos.LoadTodos()]

, и это ваше утверждение:

expect(result).toEqual(new fromActions.LoadTodos(), new fromActions.AddTodoItemSuccess(undefined));

, даже ошибка говорит вам об этом:

Expected object to be kind of LoadTodos, but was AddTodoItemSuccess...

Edit1

Вторым аргументом в методе toEqual является expectationFailOutput, который представляет собой краткую информацию, которую вы хотели бы вывести при сбое подтверждения.Обратите внимание, что в теле subscribe вы сначала получаете AddTodoItemSuccess, а затем LoadTodos - не сразу.Вероятно, это причина, по которой тест не пройден.

...