svrcekmichal / redux-axios-middleware

Redux middleware for fetching data with axios HTTP client
MIT License
920 stars 96 forks source link

getState() is cached no matter that store was changed #66

Open mgenov opened 7 years ago

mgenov commented 7 years ago

As the redux-axios-middleware is caching configuration per name of the client it's not possible different store to be passed for testing unless different client is created for each test which sounds overwhelming.

Here is a sample test that illustrates the problem:

import axios from 'axios';
import axiosMiddleware from '../src/middleware';

import { expect } from 'chai';
import configureMockStore from 'redux-mock-store';
import MockAdapter from 'axios-mock-adapter';

const options = {
  returnRejectedPromiseOnError: true,

  interceptors: {
    request: [
      ({ getState, dispatch }, config) => {
        console.log('state in interceptor: ', getState());
        config.headers['Authorization'] = 'Bearer ' + getState().access_token;
        return config;
      }
    ],
    response: [
      {
        success: ({ dispatch }, response) => {
          return response;
        },
        error: ({ dispatch, getSourceAction }, error) => {
          return Promise.reject(error);
        }
      }
    ]
  }
};

const middleware = axiosMiddleware(axios, options);

describe('axiosMiddleware', () => {
  const mockAxiosClient = new MockAdapter(axios);
  const mockStore = configureMockStore([middleware]);
  const mockAdapter = mockAxiosClient.adapter();

  afterEach(() => {
    mockAxiosClient.reset();
  });

  after(() => {
    mockAxiosClient.restore();
  });

  it('attaches authorization header on each request', () => {
    let got;

    mockAxiosClient.onGet('/test').reply(config => {
      got = config.headers['Authorization'];
      return [200];
    });

    const action = () => {
      return {
        type: 'LOAD',
        payload: {
          request: {
            url: '/test'
          }
        }
      };
    };

    const store = mockStore({ access_token: '::access_token::' });

    return store.dispatch(action()).then(() => {
      expect(got).to.equal('Bearer ::access_token::');
    });
  });

  it('attaches another authorization header on each request', () => {
    let got;

    mockAxiosClient.onGet('/test2').reply(config => {
      got = config.headers['Authorization'];
      return [200];
    });

    const action = () => {
      return {
        type: 'ANOTHER_LOAD_ACTION',
        payload: {
          request: {
            url: '/test2'
          }
        }
      };
    };

    const store = mockStore({ access_token: '::another_access_token::' });

    return store.dispatch(action()).then(() => {
      expect(got).to.equal('Bearer ::another_access_token::');
    });
  });
});

The following test fails with:

  1) axiosMiddleware attaches another authorization header on each request:

      AssertionError: expected 'Bearer ::access_token::' to equal 'Bearer ::another_access_token::'
      + expected - actual

      -Bearer ::access_token::
      +Bearer ::another_access_token::

e.g the test of first test is used as it caches the store instance that was passed no matter that another store is created using mockStore.

As better feedback I've added few loggings in the interceptor too:

return ({ getState, dispatch }) => next => action => {
    if (!middlewareOptions.isAxiosRequest(action)) {
      return next(action);
    }
    console.log(`action: ${action.type}, store value: `, getState());

and this is the execution log:

  axiosMiddleware
action: LOAD, store value:  { access_token: '::access_token::' } -> this is in the middleware
state in interceptor:  { access_token: '::access_token::' } -> this is in the interceptor 
    ✓ attaches authorization header on each request
action: ANOTHER_LOAD_ACTION, store value:  { access_token: '::another_access_token::' } -> this is in the middleware
state in interceptor:  { access_token: '::access_token::' } -> this is in the interceptor
mgenov commented 7 years ago

As possible solution middleware may keep reference to interceptors and to re-attach them before each request but this will add additional overhead.

Any thoughts on this ?

ValentinBlokhin commented 6 years ago

I have the same issue

mgenov commented 6 years ago

I've resolved it temporary in the project side by creating a new .spec.js file for each scenario but it's overwhelming if you have many scenarios.