Jest与Sinon.JS能否测试React+Redux中的增量getData逻辑?
Great question—this is a common scenario with chained thunk actions, and both Jest and Sinon are fully capable of handling it. Let’s break down why your initial approach falls short, then walk through reliable solutions.
Why Your Initial Approach Fails
Your requests array only includes the first thunk dispatch. The subsequent dispatches (for the second, third batches, etc.) happen inside the first thunk’s promise chain, so they aren’t added to the initial requests array. That’s why Promise.all(requests) only waits for the first batch to finish, leaving later actions unprocessed when you check store.getActions().
Solution 1: Track All Dispatched Thunk Promises
You can monkey-patch your Redux store’s dispatch method to collect every promise returned by thunk dispatches—including those triggered inside the initial thunk’s chain. This ensures you wait for all async work to complete before checking actions:
// Setup: Collect all dispatched thunk promises let dispatchedPromises = []; const originalDispatch = store.dispatch; store.dispatch = (...args) => { const result = originalDispatch(...args); // Thunks return promises, so we track them if (result instanceof Promise) { dispatchedPromises.push(result); } return result; }; // Trigger the initial thunk store.dispatch(actions.getData(0, 999)); // Wait for ALL dispatched thunks to resolve await Promise.all(dispatchedPromises); // Now you can safely access all actions const theActions = store.getActions(); // Assert on your actions here (e.g., check for multiple DATA_LOADED actions)
Solution 2: Wait for Expected Actions with waitFor
If you know how many actions should be dispatched in total (or can check for a final "done" action), use Jest’s waitFor (from React Testing Library, or a custom implementation) to wait until the store reflects the complete state:
Using React Testing Library’s waitFor
import { waitFor } from '@testing-library/react'; // Trigger the initial thunk store.dispatch(actions.getData(0, 999)); // Wait until the expected number of actions are present await waitFor(() => { expect(store.getActions().length).toBe(3); // Adjust to your expected count }); const theActions = store.getActions(); // Run your assertions
Custom waitFor (if you don’t use React Testing Library)
function waitFor(condition, timeout = 1000) { return new Promise((resolve, reject) => { const interval = setInterval(() => { if (condition()) { clearInterval(interval); resolve(); } }, 50); setTimeout(() => { clearInterval(interval); reject(new Error('Wait timed out waiting for actions')); }, timeout); }); } // Usage: await waitFor(() => store.getActions().some(action => action.type === 'FETCH_COMPLETE'));
Solution 3: Mock API Calls with Sinon
If you’re using Sinon to stub your API, you can wait for all stubbed calls to resolve. This works well if your thunk chains based on API responses (e.g., hasMore flag):
// Stub the API to return paginated data const fetchStub = sinon.stub(api, 'fetchData') .onFirstCall().resolves({ data: Array(1000), hasMore: true }) .onSecondCall().resolves({ data: Array(1000), hasMore: false }); // Trigger the initial thunk store.dispatch(actions.getData(0, 999)); // Wait for all API calls to resolve await Promise.all(fetchStub.returnValues); const theActions = store.getActions(); // Assert that the correct actions were dispatched for each batch
About the Manual Dispatch Approach
The manual method you mentioned (adding the second thunk to requests manually) will work for testing individual thunk logic, but it skips testing the critical chaining logic in your production code. That’s the part most likely to break, so it’s better to use one of the above solutions to test the real flow end-to-end.
内容的提问来源于stack exchange,提问作者nonopolarity

