Commit bd20aeb6 authored by Martin Hanzel's avatar Martin Hanzel Committed by Paul Slaughter

Add helpers to wait for axios requests

Add two methods to the axios_utils Jest mock:

- `waitFor(url)`, which returns a Promise that resolves when the
  next request to `url` finishes.
- `waitForAll()`, which returns a Promise that resolves when all
  pending requests finish.
parent 914af2f5
...@@ -10,21 +10,18 @@ axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest'; ...@@ -10,21 +10,18 @@ axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest';
axios.interceptors.request.use(config => { axios.interceptors.request.use(config => {
window.activeVueResources = window.activeVueResources || 0; window.activeVueResources = window.activeVueResources || 0;
window.activeVueResources += 1; window.activeVueResources += 1;
return config; return config;
}); });
// Remove the global counter // Remove the global counter
axios.interceptors.response.use( axios.interceptors.response.use(
config => { response => {
window.activeVueResources -= 1; window.activeVueResources -= 1;
return response;
return config;
}, },
e => { err => {
window.activeVueResources -= 1; window.activeVueResources -= 1;
return Promise.reject(err);
return Promise.reject(e);
}, },
); );
......
/* eslint-disable promise/catch-or-return */
import AxiosMockAdapter from 'axios-mock-adapter';
import axios from '~/lib/utils/axios_utils';
describe('axios_utils', () => {
let mock;
beforeEach(() => {
mock = new AxiosMockAdapter(axios);
mock.onAny('/ok').reply(200);
mock.onAny('/err').reply(500);
expect(axios.countActiveRequests()).toBe(0);
});
afterEach(() => axios.waitForAll().finally(() => mock.restore()));
describe('waitForAll', () => {
it('resolves if there are no requests', () => axios.waitForAll());
it('waits for all requests to finish', () => {
const handler = jest.fn();
axios.get('/ok').then(handler);
axios.get('/err').catch(handler);
return axios.waitForAll().finally(() => {
expect(handler).toHaveBeenCalledTimes(2);
expect(handler.mock.calls[0][0].status).toBe(200);
expect(handler.mock.calls[1][0].response.status).toBe(500);
});
});
});
describe('waitFor', () => {
it('waits for requests on a specific URL', () => {
const handler = jest.fn();
axios.get('/ok').finally(handler);
axios.waitFor('/err').finally(() => {
throw new Error('waitFor on /err should not be called');
});
return axios.waitFor('/ok');
});
});
});
import EventEmitter from 'events';
const axios = jest.requireActual('~/lib/utils/axios_utils').default; const axios = jest.requireActual('~/lib/utils/axios_utils').default;
axios.isMock = true; axios.isMock = true;
...@@ -13,4 +15,64 @@ axios.defaults.adapter = config => { ...@@ -13,4 +15,64 @@ axios.defaults.adapter = config => {
throw error; throw error;
}; };
// Count active requests and provide a way to wait for them
let activeRequests = 0;
const events = new EventEmitter();
const onRequest = () => {
activeRequests += 1;
};
// Use setImmediate to alloow the response interceptor to finish
const onResponse = config => {
activeRequests -= 1;
setImmediate(() => {
events.emit('response', config);
});
};
const subscribeToResponse = (predicate = () => true) =>
new Promise(resolve => {
const listener = (config = {}) => {
if (predicate(config)) {
events.off('response', listener);
resolve(config);
}
};
events.on('response', listener);
// If a request has been made synchronously, setImmediate waits for it to be
// processed and the counter incremented.
setImmediate(listener);
});
/**
* Registers a callback function to be run after a request to the given URL finishes.
*/
axios.waitFor = url => subscribeToResponse(({ url: configUrl }) => configUrl === url);
/**
* Registers a callback function to be run after all requests have finished. If there are no requests waiting, the callback is executed immediately.
*/
axios.waitForAll = () => subscribeToResponse(() => activeRequests === 0);
axios.countActiveRequests = () => activeRequests;
axios.interceptors.request.use(config => {
onRequest();
return config;
});
// Remove the global counter
axios.interceptors.response.use(
response => {
onResponse(response.config);
return response;
},
err => {
onResponse(err.config);
return Promise.reject(err);
},
);
export default axios; export default axios;
...@@ -49,17 +49,12 @@ describe('Old Notes (~/notes.js)', () => { ...@@ -49,17 +49,12 @@ describe('Old Notes (~/notes.js)', () => {
setTestTimeoutOnce(4000); setTestTimeoutOnce(4000);
}); });
afterEach(done => { afterEach(() => {
// The Notes component sets a polling interval. Clear it after every run. // The Notes component sets a polling interval. Clear it after every run.
// Make sure to use jest.runOnlyPendingTimers() instead of runAllTimers(). // Make sure to use jest.runOnlyPendingTimers() instead of runAllTimers().
jest.clearAllTimers(); jest.clearAllTimers();
setImmediate(() => { return axios.waitForAll().finally(() => mockAxios.restore());
// Wait for any requests to resolve, otherwise we get failures about
// unmocked requests.
mockAxios.restore();
done();
});
}); });
it('loads the Notes class into the DOM', () => { it('loads the Notes class into the DOM', () => {
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment