I have the following typescript function that
- tries to fetch the content of a URL,
- should time out after certain time with AbortController.
Now I want to write a unit test that simulates the timeout behavior: controller.signal should be called. I should expect an “AbortError” to be thrown.
export async function fetchScriptContents(
scriptUrl: string,
timeout?: number
): Promise<string> {
const controller = new AbortController();
const fetchTimeout = setTimeout(() => {
controller.abort(`AbortError: Fetch timeout after ${timeout} ms`);
}, timeout ?? 3000);
return await fetch(scriptUrl, { signal: controller.signal, cache: "no-cache" })
.then(async (response: Response) => {
//do something
}
.catch(async (e: Error) => {
clearTimeout(fetchTimeout);
if (e.name === "TimeoutError") {
throw new Error(`fetchScriptContents: timeout error fetching script ${scriptUrl}`);
} else if (e.name === "AbortError") {
throw new Error(`fetchScriptContents: Aborted by user action or fetch timeout: ${e.message}`);
} else if (e.name === "TypeError") {
throw new Error("fetchScriptContents: TypeError, method is not supported");
} else {
// A network error, or some other problem.
throw e;
}
});
}
This is my (multiple) attempt(s) at writing this test.
I defined a timeout of 500ms and mock the fetch implementation to resolve long after timeout (timeout + 1500ms).
if I don’t have an await
on fetchScriptContents like so:
const resultPromise = fetchScriptContents("https://vzexample.com/script.js", timeout);
I would get a resolved value but no timeout will be hit.
but if I add an await
before it
the test will always timeout. why is that? something wrong with my fetch mock? How do I properly write the test?
beforeEach(() => {
jest.clearAllMocks();
jest.useFakeTimers(); // Use fake timers for controlling timeouts
});
afterEach(() => {
jest.useRealTimers();
});
it('should throw', async () => {
const timeout = 500;
(global.fetch as jest.Mock) = jest.fn(() => new Promise((resolve, _reject) => setTimeout(() => resolve(mockResponse), timeout + 1500)));
const abortSpy = jest.spyOn(AbortController.prototype, 'abort');
jest.spyOn(global, 'setTimeout');
const resultPromise = fetchScriptContents("https://vzexample.com/script.js", timeout);
jest.runAllTimers();
expect(setTimeout).toHaveBeenCalledTimes(1);
expect(abortSpy).toHaveBeenCalledTimes(1);
await expect(fetchScriptContents("https://vzexample.com/script.js", timeout))
.rejects
.toThrow('Aborted by user action or fetch timeout');
});
I’ve read a lot about jest these recent days but I am out of ideas. Please help, thanks!