Skip to content

pekala/test-problem-example

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

2 Commits
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Problem with Jest + Redux async actions with Promises

UPDATE

After a bit of tinkering I've a working solution, which was made even nicer by one of the Jest contributors.

const flushPromises = () => new Promise(resolve => setImmediate(resolve));
test('changing the reddit downloads posts', () => {
   selectBox.simulate('change', { target: { value : 'frontend' } })
   return flushPromises().then(() => {
       expect(store.getActions()).toEqual([
           {
               type: 'REQUEST_POSTS',
               reddit: 'frontend'
           },
           {
               type: 'RECEIVE_POSTS',
               reddit: 'frontend',
               posts: [],
           }
       ])
   })
});

The flushPromises should probably be part of the Jest API, so it's more easily understandable how to handle cases like this one.

In the src/integration.test.js I would like to test whether a correct sequence of actions is dispatched in response to user interacting with the select box.

Action sequence can be retrieved using store.getActions method provided by redux-mock-store library, but it needs to be done after the redux async action thunk chain has been exhausted.

The async action dispatches one sync action right away, and then returns a Promise (which is immidiately resolved because of mocked fetch function). We need to assert on the actions sequence after the Promise chain is ran to completion. My first attempt is using Jest's jest.runAllTicks() method:

test('changing the reddit downloads posts', () => {
    jest.useFakeTimers();
    selectBox.simulate('change', { target: { value : 'frontend' } })
    jest.runAllTicks();
    expect(store.getActions()).toEqual([
        {
            type: 'REQUEST_POSTS',
            reddit: 'frontend'
        },
        {
            type: 'RECEIVE_POSTS',
            reddit: 'frontend',
            posts: [],
        }
    ])
});

But this doesn't work, i.e. only the synchronously dispatched action has beed dispatched before assertion.

Another, more hacky attempt is to use setTimeout(..., 0) to get run assertion strictly after Promise micro-queue.

test('changing the reddit downloads posts', done => {
    selectBox.simulate('change', { target: { value : 'frontend' } })
    setTimeout(() => {
        expect(store.getActions()).toEqual([
            {
                type: 'REQUEST_POSTS',
                reddit: 'frontend'
            },
            {
                type: 'RECEIVE_POSTS',
                reddit: 'frontend',
                posts: [],
            }
        ])
        done()
    }, 0)
});

While this does work, and all actions have been dispatched to the store before assertion, there is a strange Jest behaviour if the assertion is incorrect - it just hangs and after a while fails (e.g. a typo in action type) with Timeout - Async callback was not invoked within timeout specified by jasmine.DEFAULT_TIMEOUT_INTERVAL..

Another way I tried is returning a Promise from the test, but since we don't have handle to the actual Promise chain of the async action, we can't reliably place ourselves after the end of that chain, i.e. this doesn't work:

return Promise.resolve().then(() => {
    // assertion
});

but this works (also, fails correctly with typo):

return Promise.resolve().then(() => {}).then(() => {
    // assertion
});

which means this method is dependent on the length of the promise chain we're testing.

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published