I am trying to test this epic https://github.com/zarcode/unsplashapp/blob/master/src/epics/photos.js . Problem is that map
never happens when I run test (which I assume means that Promise never resolves), so photosSuccess
action never happens also:
export const loadPhotosToList = (action$: Observable<Action>, state$:
Object): Observable<Action> => {
const state = (photosFilter: PhotosFilter) => state$.value.photos[photosFilter];
return action$
// .ofType(ACTION.FETCH_PHOTOS_REQUESTED)
.pipe(
filter((a: Action) =>
a.type === ACTION.FETCH_PHOTOS_REQUESTED &&
((state(a.filter).loadingState === 'idle' && !state(a.filter).isLastPage) || a.refresh)),
switchMap((a) => {
const nextPage = !a.refresh ? state(a.filter).lastLoadedPage + 1 : 1;
const loadingAction = of(photosActions.photosLoading(a.filter, a.refresh));
const request = api.fetchPhotos({
page: nextPage,
per_page: perPage,
order_by: a.filter,
});
const requestAction = from(request)
.pipe(
// tap(data => { console.log("data", data); }),
map(data =>
photosActions.photosSuccess(
data,
a.filter,
nextPage,
data.length < perPage,
a.refresh,
)),
catchError(e => of(photosActions.photosFail(e.message, a.filter))),
);
// requestAction.subscribe(x => console.log("-------",x));
return loadingAction
.pipe(
concat(requestAction),
takeUntil(action$
.pipe(filter(futureAction => futureAction.type === ACTION.FETCH_PHOTOS_REQUESTED))),
);
}),
);
};
However, if I do requestAction.subscribe
promise gets resolved and I get the result in the console log.
Note: this happens only when I run this test https://github.com/zarcode/unsplashapp/blob/master/src/epics/photos.test.js, app code works fine, data is fetching fine.
Question is: How to write this test properly?
Keep mind when testing async code you need to use different strategies, as noted in the documentation.
I crossed with a lot of problems before trying to test my async code, most of the problems were that the test was asserting the expected behavior before the resolved promise could be processed, and the assertion needed to be made in the next tick of the event loop, which I in practice achieve by putting the assertion inside a setImmediate
, which can be achieved with a setTimeout
too.
From the documentation of jest, you can change your test and pass the done
callback, dispatch the triggering action as normal, but put the assertion inside a setTimeout
callback with 1 ms timeout, just to allow the resolved promised to be processed, then the callback will be called and it will assert the desired state.
Other strategies can be used, like async/await
, or you can instead of using setTimeout
, resolve an empty Promise and put the assertion into the then
callback. Just keep in mind that if more Promises exist within the tested flow, more ticks on the event loop will be necessary until the desired output is achieved to be asserted.
EDIT: Implementation:
// test
it('produces the photo model', (done) => {
...
// dispatch epic triggering action
store.dispatch(...);
// assertion
setImmediate(() => {
expect(store.getActions()).toEqual([
...
]);
done();
});
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With