2

I was working on a functionality which involved fetching the data from an API and rendering the components accordingly.

index.js

import React, { useState, useEffect } from 'react';
import PropTypes from 'prop-types';
import StarRating from './components/StarRating';
import {
  API_URL,
  API_TIMEOUT,
} from '../../../../someLoc';

export async function fetchWithTimeout(resource, options = {}) {
  const { timeout } = options;

  const controller = new AbortController();
  const id = setTimeout(() => controller.abort(), timeout);
  const response = await fetch(resource, {
    ...options,
    signal: controller.signal,
  });
  clearTimeout(id);
  return response;
}
const UserRating = ({ productId }) => {
  const [loading, setLoading] = useState(true);
  const [data, setData] = useState({});
  const [error, setError] = useState(false);
  useEffect(
    () => {
      const fetchReviewsNRatings = async (bvUrl, bvTimeout) => {
        const url = `${bvUrl}:${productId}`;
        const response = await fetchWithTimeout(url, { timeout: bvTimeout });
        if (response.ok) {
          const ratingData = await response.json();
          setData(ratingData);
          setLoading(false);
        } else {
          setError(true);
          setLoading(false);
        }
      };
      fetchReviewsNRatings(API_TIMEOUT);
    },
    [productId],
  );
  // I didn't know how to test this branch if I simply returned null. so wrapped null inside of div. 

// MY INTENT HERE: if(loading) return null;
  if (loading) return <div className="test-class">{null}</div>;
  // destructuring the response to get the data once loading is complete.
  const {
    Results: [
      {
        ABC: {
          DEF: { GHI, JKL },
        },
      },
    ],
  } = data;
  const numReviews = +GHI;
  const value = JKL.toFixed(1);

  return !error && <StarRating value={value} numReviews={numReviews} />;
};

UserRating.propTypes = {
  productId: PropTypes.string,
};

export default UserRating;

index.test.js

import React from 'react';
import { mount } from 'enzyme';
import { act } from 'react-dom/test-utils';
import UserRating from '..';
import { PRODUCT_ID } from '../../../../../someLoc';

describe('Testing for <UserRating /> component', () => {
  const data = {
    Results: [
      {
        ABC: {
          DEF: {
            GHI: 4.567,
            JKL: 1103,
          },
        },
      },
    ],
  };
  const productId = PRODUCT_ID;
  let component = null;
  beforeEach(() => {
    global.fetchWithTimeout = jest.fn(() =>
      Promise.resolve({
        ok: true,
        status: 200,
        data,
        json: () => data,
      }),
    );
    global.fetch = jest.fn(() =>
      Promise.resolve({
        ok: true,
        status: 200,
        data,
        json: () => data,
      }),
    );
    component = act(() => mount(<UserRating productId={productId} />));
  });

  it('should call fetch', async () => {
    expect(component.exists()).toBeTruthy();
    // Testting for loading state.
    expect(component.find('.test-class').exists()).toBe(true);
  });

What's working as expected:

  • The functionality that is expected.
  • The above test which I managed to write is passing for win-win scenario i.e. when the data is loaded and fetched successfully.

MY QUESTION:

a. I want to write a unit test in such a way so as to test what happens if there is some error from server side(5xx) or from client side(4xx).

b. What else can I improve in my passing test scenario?

What I have tried:

describe('Testing for <UserRating /> component for failed response', () => {
  const data = { message: '500: Internal Server Error' };
  const error = true;
  const productId = PRODUCT_ID;
  let component = null;
  beforeEach(() => {
    global.fetchWithTimeout = jest.fn(() =>
      Promise.reject(
        new Error({
          ok: false,
          status: 500,
          data,
          json: () => data,
        }),
      ),
    );
    global.fetch = jest.fn(() =>
      Promise.reject(
        new Error({
          ok: false,
          status: 500,
          data,
          json: () => data,
        }),
      ),
    );
    component = act(() => mount(<UserRating productId={productId} />));
  });

  it('should not fetch', async () => {
    expect(component.exists()).toBeFalsy();
    expect(component.find('.test-class').exists()).toBe(true);
    console.debug(component);
  });
});

When I tried the above code it is breaking the win-win scenario as well with the error message cannot read property exists for null. I am relatively new to enzyme and facing issues while using enzyme for writing tests for hooks. I know that React testing library is preferrable but my organization is yet to shift onto that. I've to write fail case as well since the coverage for branches is poor. I'll be highly obliged for any help. Thanks

REFFERED LINKS and Questions:

  1. how-to-test-async-data-fetching-react-component-using-jest-and-enzyme
  2. Testing hooks with mount
  3. Should a Promise.reject message be wrapped in Error?
BaijNR
  • 46
  • 5

0 Answers0