Mocking Fetch Calls with Jasmine

January 10th 2020 Unit Testing Ionic 4/5 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.

If you're looking for online one-on-one mentorship on a related topic, you can find me on Codementor.
If you need a team of experienced software engineers to help you with a project, contact us at Razum.
Copyright
Creative Commons License