Redux Waitfor Middleware

raw JSON →
1.0.1 verified Thu Apr 23 auth: no javascript

Redux Waitfor Middleware (current stable version 1.0.1) is a lightweight utility designed to facilitate testing of Redux applications, particularly for asynchronous flows. It operates as a standard Redux middleware, recording dispatched actions and providing a `waitFor` method that allows tests to pause execution until a specific set of actions has been dispatched within a given timeout. The library is primarily used in testing environments to assert on the sequence and payload of actions after asynchronous operations complete, such as API calls. Its release cadence appears to be stable, with the current version being quite mature. A key differentiator is its simplicity and direct focus on action-based waiting, making it less verbose than complex mocking or promise-chaining in some test scenarios. It also handles the case where actions are already dispatched, immediately resolving the wait.

error Error: Timeout of XXXXms reached waiting for actions: ACTION_TYPE_A, ACTION_TYPE_B
cause One or more of the specified action types were not dispatched within the allocated timeout period.
fix
Verify that the actions are indeed dispatched by your application logic, increase the timeout value if the operation genuinely takes longer, or ensure that clean() isn't prematurely clearing an action you're waiting for from a prior test.
error TypeError: Cannot read properties of undefined (reading 'waitFor')
cause The `waitForMiddleware` instance was not correctly created or is out of scope. The `createWaitForMiddleware` function returns an object with `waitFor` and `clean` methods.
fix
Ensure const waitForMiddleware = createWaitForMiddleware(); is called and waitForMiddleware is accessible where you are attempting to use its methods. It's often passed around or made available via a setup function in testing frameworks.
gotcha Actions dispatched before the `waitFor` call but after the last `clean()` call will immediately resolve the `waitFor` promise. This can lead to flaky tests if `clean()` is not called consistently between test cases.
fix Always call `waitForMiddleware.clean()` at the beginning or end of each individual test case (e.g., in `beforeEach` or `afterEach` hooks) to ensure a fresh state for each test.
breaking The `waitFor` method throws an `Error` if the specified actions are not dispatched within the provided timeout. This will fail the test by default and needs to be handled if non-receipt of an action is an expected test outcome.
fix Wrap `await waitForMiddleware.waitFor(...)` calls in a `try...catch` block if you need to specifically handle timeout scenarios without failing the test, though typically, a timeout indicates a test failure.
npm install redux-waitfor-middleware
yarn add redux-waitfor-middleware
pnpm add redux-waitfor-middleware

This quickstart demonstrates how to integrate `redux-waitfor-middleware` into a Redux store, simulate an asynchronous action flow, and then use `waitForMiddleware.waitFor()` to assert that a specific action ('FETCH_DATA_SUCCESS') is dispatched within a given timeout.

import createWaitForMiddleware from 'redux-waitfor-middleware';
import { createStore, applyMiddleware } from 'redux';

// A dummy reducer for demonstration
const exampleReducer = (state = { data: [] }, action) => {
  switch (action.type) {
    case 'FETCH_DATA_REQUEST':
      return { ...state, loading: true };
    case 'FETCH_DATA_SUCCESS':
      return { ...state, loading: false, data: action.payload };
    case 'FETCH_DATA_FAILURE':
      return { ...state, loading: false, error: action.error };
    default:
      return state;
  }
};

const waitForMiddleware = createWaitForMiddleware();
const store = createStore(exampleReducer, applyMiddleware(waitForMiddleware));

async function simulateFetch() {
  store.dispatch({ type: 'FETCH_DATA_REQUEST' });
  console.log('Dispatched FETCH_DATA_REQUEST');

  // Simulate an async operation
  setTimeout(() => {
    store.dispatch({ type: 'FETCH_DATA_SUCCESS', payload: ['item1', 'item2'] });
    console.log('Dispatched FETCH_DATA_SUCCESS');
  }, 100);
}

async function runTest() {
  console.log('Running test...');
  simulateFetch();

  try {
    const successActions = await waitForMiddleware.waitFor(['FETCH_DATA_SUCCESS'], 500);
    console.log('Test passed! Received actions:', successActions);
    console.assert(successActions.length === 1, 'Expected one success action');
    console.assert(successActions[0].payload.includes('item1'), 'Payload check');
  } catch (error) {
    console.error('Test failed:', error.message);
  } finally {
    // Always clean up after a test
    waitForMiddleware.clean();
    console.log('Cleaned recorded actions.');
  }
}

runTest();