Mocking Fetch Calls with Jasmine

January 10th 2020 Unit Testing Ionic 4 Angular

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.

Copyright
Creative Commons License