Updates
September 29 2020: refactor to use
fakeTime
andsubscribeAndSpyOn
as recommended by Shai Reznik πOctober 07 2020: reafactor to use
subscribeSpyTo
as recommended by Shai Reznik π
Have you try theobserver-spy library by Shai Reznik?
It particularly makes testing ngrx effects an easy task and keeps them readable.
To demonstrate this, I refactored the tests from book.effects.spec.ts
from the ngrx example application, and here are the differences...
Testing the success path
Using marbles:
it('should return a book.SearchComplete, with the books, on success, after the de-bounce', () => {
const book1 = { id: '111', volumeInfo: {} } as Book;
const book2 = { id: '222', volumeInfo: {} } as Book;
const books = [book1, book2];
const action = FindBookPageActions.searchBooks({ query: 'query' });
const completion = BooksApiActions.searchSuccess({ books });
actions$ = hot('-a---', { a: action });
const response = cold('-a|', { a: books });
const expected = cold('-----b', { b: completion });
googleBooksService.searchBooks = jest.fn(() => response);
expect(
effects.search$({
debounce: 30,
scheduler: getTestScheduler(),
})
).toBeObservable(expected);
});
Using observer-spy:
it('should return a book.SearchComplete, with the books, on success, after the de-bounce', fakeTime((flush) => {
const book1 = { id: '111', volumeInfo: {} } as Book;
const book2 = { id: '222', volumeInfo: {} } as Book;
const books = [book1, book2];
actions$ = of(FindBookPageActions.searchBooks({ query: 'query' }));
googleBooksService.searchBooks = jest.fn(() => of(books));
const effectSpy = subscribeSpyTo(effects.search$());
flush();
expect(effectSpy.getLastValue()).toEqual(
BooksApiActions.searchSuccess({ books })
);
})
);
Testing the error path
Using marbles:
it('should return a book.SearchError if the books service throws', () => {
const action = FindBookPageActions.searchBooks({ query: 'query' });
const completion = BooksApiActions.searchFailure({
errorMsg: 'Unexpected Error. Try again later.',
});
const error = { message: 'Unexpected Error. Try again later.' };
actions$ = hot('-a---', { a: action });
const response = cold('-#|', {}, error);
const expected = cold('-----b', { b: completion });
googleBooksService.searchBooks = jest.fn(() => response);
expect(
effects.search$({
debounce: 30,
scheduler: getTestScheduler(),
})
).toBeObservable(expected);
});
Using observer-spy:
it('should return a book.SearchError if the books service throws', fakeTime((flush) => {
const error = { message: 'Unexpected Error. Try again later.' };
actions$ = of(FindBookPageActions.searchBooks({ query: 'query' }));
googleBooksService.searchBooks = jest.fn(() => throwError(error));
const effectSpy = subscribeSpyTo(effects.search$());
flush();
expect(effectSpy.getLastValue()).toEqual(
BooksApiActions.searchFailure({
errorMsg: error.message,
})
);
})
);
Testing when the effect does not do anything
Using marbles:
it(`should not do anything if the query is an empty string`, () => {
const action = FindBookPageActions.searchBooks({ query: '' });
actions$ = hot('-a---', { a: action });
const expected = cold('---');
expect(
effects.search$({
debounce: 30,
scheduler: getTestScheduler(),
})
).toBeObservable(expected);
});
Using observer-spy:
it(`should not do anything if the query is an empty string`, fakeTime((flush) => {
actions$ = of(FindBookPageActions.searchBooks({ query: '' }));
const effectSpy = subscribeSpyTo(effects.search$());
flush();
expect(effectSpy.getLastValue()).toBeUndefined();
})
You can find the working test here:
What do you think? Which one do you prefer?
Top comments (8)
Great job Alfredo!
Few things I would try that might make your tests even shorter -
You can use fakeTime and
flush()
to remove the need of using the TestScheduler.You can use the factory function subscribeSpyTo to subscribe and create the spy at the same time (and you can also auto-unsubscribe as well)
Let me know if it helps
Thank you, Shai!
I think it looks better, readable and fewer lines of code. Updated the article and repo.
I was not able to find
subscribeSpyTo
and usedsubscribeAndSpyOn
instead.Awesome Alfredo!
Try to update to the latest version (1.4.0) and you'll see
subscribeSpyTo
Wooot!
I will update the repo and the article. Thanks!
Nice!
Pay attention that you've missed 1
subscribeAndSpyOn
... πThanks! it should be good now =)
Pair programming FTW! πͺπ
Been loving this lib! Had missed the recent updates which make it even cleaner to use.