Merge branch 'mh/wait-for-requests' into 'master'

Add helper to wait for axios requests in frontend tests

Closes #60972

See merge request gitlab-org/gitlab-ce!30887
This commit is contained in:
Paul Slaughter 2019-09-05 12:56:18 +00:00
commit 95ef272539
4 changed files with 113 additions and 14 deletions

View file

@ -10,21 +10,18 @@ axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest';
axios.interceptors.request.use(config => {
window.activeVueResources = window.activeVueResources || 0;
window.activeVueResources += 1;
return config;
});
// Remove the global counter
axios.interceptors.response.use(
config => {
response => {
window.activeVueResources -= 1;
return config;
return response;
},
e => {
err => {
window.activeVueResources -= 1;
return Promise.reject(e);
return Promise.reject(err);
},
);

View file

@ -0,0 +1,45 @@
/* 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');
});
});
});

View file

@ -1,3 +1,5 @@
import EventEmitter from 'events';
const axios = jest.requireActual('~/lib/utils/axios_utils').default;
axios.isMock = true;
@ -13,4 +15,64 @@ axios.defaults.adapter = config => {
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;

View file

@ -49,17 +49,12 @@ describe('Old Notes (~/notes.js)', () => {
setTestTimeoutOnce(4000);
});
afterEach(done => {
afterEach(() => {
// The Notes component sets a polling interval. Clear it after every run.
// Make sure to use jest.runOnlyPendingTimers() instead of runAllTimers().
jest.clearAllTimers();
setImmediate(() => {
// Wait for any requests to resolve, otherwise we get failures about
// unmocked requests.
mockAxios.restore();
done();
});
return axios.waitForAll().finally(() => mockAxios.restore());
});
it('loads the Notes class into the DOM', () => {