Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to test redux-thunk middleware async functions?

I'm trying to test my asyn thunk middleware function using mocha, chai and sinon (my first time!).

Please consider my files:

ayncActionCreators.js

export const fetchCurrentUser = () => {
   return (dispatch) => {
      setTimeout(dispatch, 100);
   }
};

ayncActionCreators.spec.js

//...
it('Should work', () => {
   const dispatch = sinon.spy();
   const action = fetchCurrentUser();

   action(dispatch);

   expect(dispatch.called).to.be.true;
});

I did not yet implement the fetchCurrentUser function - just assumed it will take some "server" time and then it will call 'dispatch()'.

The spec fails, due to the async flow. If I add a setTimeout of 101 ms before the expect - it passes.

My code will use some DB API that returns promise, so the async function will eventually look like:

//...
return (dispatch) => {
   return dbAPI.fetchUser().then(dispatch(....));
}

So I tried to require dbAPI and create a sinon.stub().returns(Promise.resolve()) inside the test and it didn't work as well (I thought that since the stub returns a resolved promise - the async function will act like a synchronous function).

Any ideas how should I test async functions like that?

Thank, Amit.

like image 985
Amit Kaspi Avatar asked Jan 18 '16 18:01

Amit Kaspi


2 Answers

Don't mock dispatch with sinon, write your own and call Mocha's done() in that when it's done.

it('Should work', (done) => {
   const dispatch = () => {
     // Do your tests here
     done();
   };
   const action = fetchCurrentUser();

   action(dispatch)
     // Also allow quick failures if your promise fails
     .catch(done);
})

If you're just wanting to ensure that the dispatch is called, then mocha will time out. The catch on the returned promise from your async action creator allows errors to be shown in the right place and for the test to fail rather than time out.

like image 50
El Yobo Avatar answered Oct 28 '22 13:10

El Yobo


Well, I think I've found a solution:

Assuming my async function looks like this:

//...
return (dispatch) => {
   return dbAPI.fetchUser().then(dispatch(....));
}

Then I can write the spec as follows:

it('Should work', () => {
   dbAPI.fetchUser = sinon.stub().returns(Promise.resolve({username: 'John'}));

   const dispatch = sinon.spy();
   const action = fetchCurrentUser();

   action(dispatch).then(() => {
      expect(dispatch.called).to.be.true;
   });
});

I don't know if this is a workaround or not, but it works. I would appreciate your opinions of a better way of doing this...

Thanks, Amit.

like image 39
Amit Kaspi Avatar answered Oct 28 '22 14:10

Amit Kaspi