Testing Javascript with iterables
03 Apr 2018 | javascript async promise generatorI wanted to write tests for the retryAxios
function described in my recent post
about retrying back-end service calls.
Rather than mocking Axios itself with either moxios or Axios mock adapter, I wanted a simple mock of an Axios function. The mock needs to be configurable to return a known sequence of responses, where each response is a promise that either resolves or rejects.
My solution was to create a function that returns a closure:
const mockAxios = (...returns) => {
const iter = returns[Symbol.iterator]();
return () => {
const { done, value } = iter.next();
if (done) {
throw new Error('Testing error: mockAxios iterator done');
}
return mockRequest(value);
};
};
- The rest parameter
returns
contains an array that specifies how each function call will return. mockAxios
returns a closure that iterates throughreturns
, callingmockRequest
for each value in that iterable.- If the iterable is exhausted the test itself is in error.
mockRequest
is a bare-bones implementation of the behaviour of Axios functions:
class HttpError extends Error {
constructor(response, ...params) {
super(...params);
this.response = response;
}
}
class NetError extends Error {
constructor(code, ...params) {
super(...params);
this.code = code;
}
}
const mockRequest = async (retVal) => {
if (Number.isInteger(retVal)) {
const response = { status: retVal };
if (retVal >= 400 && retVal <= 599) {
throw new HttpError(response);
}
return response;
}
throw new NetError(retVal);
};
- If
retVal
is an HTTP success status code, it returns aresponse
object with that code. - If
retVal
is an HTTP failure status code, it throws a simplified HTTP error object with that code. - Otherwise it returns a simplified network error object with code
retVal
. mockRequest
isasync
so it wraps the return value in a resolved or rejected promise.
Here is a simple example of using mockAxios
in a Jest
test case, with inline comments for annotation:
test('retryAxios should return first successful call if within retry limit', async () => {
// Calling expect.assertions() is useful when testing asynchronous code.
expect.assertions(1);
const data = await retryAxios(
// The iterable with delays after retriable failures.
[5, 5],
// A mock Axios function that returns server failure, then network failure,
// then success.
mockAxios(500, 'ECONNRESET', 301)
);
expect(data.status).toBe(301);
});
The test code has been added to my Gist for retryAxios.