Middleware Test Structure

Let’s have a look at the structure we will use for middleware testing.

We'll cover the following

The middleware is where most of the complex logic of our application will reside. Because middleware has full access to the store’s dispatch() and getState() methods, as well as control over the actions’ flow via next(), middleware can become quite complex, with nontrivial asynchronous flows.

Middleware test structure

At their core, middleware are functions that receive actions to process, albeit with a complicated signature. Redux makes the first two function calls during startup. Middleware only makes the last call dynamically when new actions need to be processed.

The basic signature looks like this:

function sampleMiddleware({ dispatch, getState }) {
  return function nextWrapper(next) {
    return function innerCode(action) {
      // TODO: Implement the middleware
      next(action);
   }
 }
}

To test middleware we will need to mock dispatch(), getState(), and mock() and call sampleMiddleware() and nextWrapper() to get our test target, the innerCode() function:

const next       = jest.fn();
const dispatch   = jest.fn();
const getState   = jest.fn();
const middleware = apiMiddleware({ dispatch, getState })(next);

We can now use the regular Jest tests to test the middleware() function we built by calling it with action objects:

it('should process action', () => {
  const next       = jest.fn();
  const dispatch   = jest.fn();
  const getState   = jest.fn();
  const middleware = sampleMiddleware({ dispatch, getState })(next);

  const sampleAction = { type: 'SAMPLE_ACTION' };

  middleware(sampleAction);

  // TODO: Add expects
});

In the case of our simple middleware, we only want to verify that it passed the action correctly down the chain by calling next(action). Since we used Jest’s function mocking, we can get a full history of calls to each mock by accessing next.mock.calls:

// Verifying correct calls to next()
expect(next.mock.calls.length).toBe(1);
expect(next.mock.calls[0].length).toBe(1);
expect(next.mock.calls[0][0]).toEqual(sampleAction);

Our test verified that there was only one call to next(). In that call, there was only one parameter passed, and that parameter was the sample action.

We could do all three tests in one go by using:

// Combining the test cases
expect(next.mock.calls).toEqual([[sampleAction]]);

Get hands-on with 1200+ tech skills courses.