Reputation: 1120
I am writing unit-test for Angular ngrx/effects, for a todo app. I am using ReplaySubject() as they are more intuitive and easy to test, instead of jasmine marbels (hot and cold).
But i am getting following error.
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))
);
});
});
Screenshot of error
I referred to ngrx documentation, but it did not have any example of mergeMap. How to write the test for effects where multiple actions are dispatched using mergeMap?
Upvotes: 0
Views: 4026
Reputation: 1120
Finally figured out, thanks @timdeschryver for hints.
You need take
and skip
operators of rxjs to test if the first and second actions are dispatched as expected.
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))
);
});
Upvotes: 4
Reputation: 2416
This is order of returned value in loadTodo$
effect:
[new fromTodos.AddTodoItemSuccess(data), new fromTodos.LoadTodos()]
and this is your assertion:
expect(result).toEqual(new fromActions.LoadTodos(), new fromActions.AddTodoItemSuccess(undefined));
even error tolds you about that:
Expected object to be kind of LoadTodos, but was AddTodoItemSuccess...
Edit1
Second argument in toEqual
method is expectationFailOutput
which is short info that you would like to output when assertion fails. Note that in subscribe
body you firstly get AddTodoItemSuccess
and then LoadTodos
- not at once. Probably this is reason why test fails.
Upvotes: 0
Reputation: 15505
Your effect returns two actions new fromTodos.AddTodoItemSuccess(data), new fromTodos.LoadTodos()
.
In your test you use expect(result).toEqual(new fromActions.LoadTodos(), new fromActions.AddTodoItemSuccess(undefined));
, which checks if the action returned is LoadTodos.
Because the second action returned is the success action, we still verify if the action is the LoadTodos
action, resulting in the error.
Upvotes: 1