Search code examples
javascriptreduxjestjsfetchredux-saga

Jest redux saga API call with response image


I'm trying to test a fetch that responds with a readable stream for an image, but I'm stuck and not sure how to approach this specific case. Is there a simple, easy way to mock the call's response?

Any help would be appreciated :D

The generator function looks like the following:

export function* getImageSaga () {

    try {
        const headers = {
            Accept: 'image/jpeg',
        };
        const options = {
            headers,
            method: 'GET',
        };

        const response = yield call(fetch, GET_IMAGE_API, options);
        const blob = yield response.blob();
        const url = URL.createObjectURL(blob);
        yield put(getImageSuccess(url));
    } catch (error) {
        yield put(getImageError(error));
    }
}

Solution

  • You can use testing the saga generator function step-by-step approach.

    Before testing the saga generator function, you need to be aware of these things:

    1. jsdom doesn't support fetch yet, see issue#1724

    2. jsdom doesn't support URL.createObjectURL API yet, see issue#1721

    3. By default, jestjs uses jsdom as a test environment to simulate the browser-like environment.

    So I will use isomorphic-fetch polyfill for fetch, and mock URL.createObjectURL() API and its return value. Besides, you can also use the Response() constructor to create your own custom Response object:

    const response = new Response();
    

    We will use this custom response object as the mock return value of fetch.

    The test will be:

    index.ts:

    import { call, put } from 'redux-saga/effects';
    
    export const getImageSuccess = (payload) => ({ type: 'GET_IMAGE_SUCCESS', payload });
    export const getImageError = (payload) => ({ type: 'GET_IMAGE_ERROR', payload });
    export const GET_IMAGE_API = 'http://localhost:3000/api/image';
    
    export function* getImageSaga() {
      try {
        const headers = {
          Accept: 'image/jpeg',
        };
        const options = {
          headers,
          method: 'GET',
        };
    
        const response = yield call(fetch, GET_IMAGE_API, options);
        const blob = yield response.blob();
        const url = URL.createObjectURL(blob);
        yield put(getImageSuccess(url));
      } catch (error) {
        yield put(getImageError(error));
      }
    }
    

    index.test.ts:

    import { call, put } from 'redux-saga/effects';
    import { getImageSaga } from './';
    
    describe('72521882', () => {
      test('should get image success', () => {
        const objectUrl = 'blob:https://yari-demos.prod.mdn.mozit.cloud/8b065c5a-fd18-44d8-9ff5-5c29407a88b7';
        Object.defineProperty(window.URL, 'createObjectURL', { value: () => objectUrl });
    
        const gen = getImageSaga();
    
        expect(gen.next().value).toEqual(
          call(fetch, 'http://localhost:3000/api/image', {
            headers: {
              Accept: 'image/jpeg',
            },
            method: 'GET',
          }),
        );
        const mResponse = new Response();
        gen.next(mResponse);
    
        expect(gen.next().value).toEqual(put({ type: 'GET_IMAGE_SUCCESS', payload: objectUrl }));
      });
    });
    

    Test result:

     PASS   redux-saga-examples  packages/redux-saga-examples/src/stackoverflow/72521882/index.test.ts
      72521882
        ✓ should get image success (4 ms)
    
    ----------|---------|----------|---------|---------|-------------------
    File      | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s 
    ----------|---------|----------|---------|---------|-------------------
    All files |   88.89 |      100 |   66.67 |   91.67 |                   
     index.ts |   88.89 |      100 |   66.67 |   91.67 | 22                
    ----------|---------|----------|---------|---------|-------------------
    Test Suites: 1 passed, 1 total
    Tests:       1 passed, 1 total
    Snapshots:   0 total
    Time:        3.648 s
    

    jest.setup.js:

    import 'isomorphic-fetch';
    
    jest.setTimeout(10 * 1000);
    

    jest.config.js:

    {
      setupFilesAfterEnv: ['./jest.setup.js']
    }