Mocking Fetch Calls with Jasmine
After getting asset loading working in Ionic unit tests, I wanted to also test the error handling in case an asset fails to load (e.g. because it's missing). Mocking the fetch
call would be the easiest way to do that.
I started with jasmine-ajax which was designed for faking HTTP responses:
import 'jasmine-ajax';
describe('mocked fetch calls', () => {
beforeEach(() => {
jasmine.Ajax.install();
jasmine.Ajax.stubRequest(/.*\/assets\/sample.json/).andError({
status: 404,
statusText: 'HTTP/1.1 404 Not Found'
});
});
afterEach(() => {
jasmine.Ajax.uninstall();
});
it('should handle failed asset load', async () => {
try {
await fetch('./assets/sample.json');
fail('Asset should fail to load.');
} catch {}
});
});
The following two NPM packages are required for the above code to work:
npm i jasmine-ajax --save-dev
npm i @types/jasmine-ajax --save-dev
Unfortunately, jasmine-ajax can't mock fetch
calls, only XHR requests. However, Marcelo Lazaroni wrote a blog post explaining how it can be made to work with fetch
by installing a fetch
polyfill that uses XHR requests even in browser supporting fetch
natively:
window.fetch = undefined;
import 'whatwg-fetch';
However, he replaced the native fetch
implementation permanently which I didn't like. After reading the fetch
polyfill documentation I found a way to revert fetch
to native implementation after I'm done with mocking:
import { fetch as fetchPolyfill } from 'whatwg-fetch';
// replace native fetch with polyfill
const originalFetch = (window as any).fetch;
(window as any).fetch = fetchPolyfill;
// ...
// restore native fetch implementation
(window as any).fetch = originalFetch;
Here's the full sample test from above using this approach:
import { fetch as fetchPolyfill } from 'whatwg-fetch';
import 'jasmine-ajax';
describe('mocked fetch calls', () => {
let originalFetch;
beforeEach(() => {
originalFetch = (window as any).fetch;
(window as any).fetch = fetchPolyfill;
jasmine.Ajax.install();
jasmine.Ajax.stubRequest(/.*\/assets\/sample.json/).andError({
status: 404,
statusText: 'HTTP/1.1 404 Not Found'
});
});
afterEach(() => {
jasmine.Ajax.uninstall();
(window as any).fetch = originalFetch;
});
it('should handle failed asset load', async () => {
try {
await fetch('./assets/sample.json');
fail('Asset should fail to load.');
} catch {}
});
});
Of course, the fetch
polyfill package needs to be installed for this to work:
npm i whatwg-fetch --save-dev
Replacing native fetch
implementation with a polyfill is not a perfect solution but since I'm doing it only in tests, the benefit of being able to mock it makes it worth it in my opinion.