Search code examples
reactjsunit-testingsinonstublottie

How to mock a module import with Sinon and ReactJS


I'm trying to write a unit test for one of my React components written in TS:

import React, { useContext } from 'react';
import Lottie from 'lottie-react-web';

import { ConfigContext } from '../ConfigProvider';
import type { UIKitFC } from '../../types/react-extensions';

// interfaces
export interface LoadingOverlayProps {
  size: 'large' | 'medium' | 'small';
  testId?: string;
}

interface LoaderProps {
  size: 'large' | 'medium' | 'small';
}

const G3Loader: React.FC<LoaderProps> = ({ size }) => {
  const options = { animationData };
  const pxSize =
    size === 'small' ? '100px' : size === 'medium' ? '200px' : '300px';
  const height = pxSize,
    width = pxSize;

  return (
    <div className="loader-container">
      <Lottie options={options} height={height} width={width} />
      <div className="loader__loading-txt">
        <div>
          <h4>Loading...</h4>
        </div>
      </div>
    </div>
  );
};



/**
 * Description of Loading Overlay component
 */
export const LoadingOverlay: UIKitFC<LoadingOverlayProps> = (props) => {
  const { testId } = props;

  const { namespace } = useContext(ConfigContext);
  const { baseClassName } = LoadingOverlay.constants;

  const componentClassName = `${namespace}-${baseClassName}`;

  const componentTestId = testId || `${namespace}-${baseClassName}`;

  return (
    <div id={componentTestId} className={componentClassName}>
      <G3Loader size={props.size} />
    </div>
  );
};

LoadingOverlay.constants = {
  baseClassName: 'loadingOverlay',
};

LoadingOverlay.defaultProps = {
  testId: 'loadingOverlay',
};

export default LoadingOverlay;

The component uses an imported module "Lottie" for some animation, but I'm not interested in testing it, I just want to test my component and its props.

The problem is, when I run my unit test, I get an error: Error: Not implemented: HTMLCanvasElement.prototype.getContext (without installing the canvas npm package)

After some research, I've concluded that the error is caused by the Lottie import so I would like to mock it for the purpose of my test. I'm using Mocha and Sinon's stub functionality to try and mock the library import, but the same error persists, making me feel like I'm not stubbing the module out correctly. Here's my latest attempt at a unit test:

import React from 'react';
import * as Lottie from 'lottie-react-web';
import { render } from '@testing-library/react';
import { expect } from 'chai';
import * as sinon from 'sinon';

import LoadingOverlay from '../src/components/LoadingOverlay';

const TEST_ID = 'the-test-id';

const FakeLottie: React.FC = (props) => {
  return <div>{props}</div>;
};

describe('Loading Overlay', () => {
  //   beforeEach(function () {
  //     sinon.stub(Lottie, 'default').callsFake((props) => FakeLottie(props));
  //   });

  console.log('11111');
  it('should have a test ID', () => {
    sinon.stub(Lottie, 'default').callsFake((props) => FakeLottie(props));
    console.log(Lottie);
    const { getByTestId, debug } = render(
      <LoadingOverlay testId={TEST_ID} size="small" />
    );

    debug();
    expect(getByTestId(TEST_ID)).to.not.equal(null);
  });
});

I'm not really sure what else to try, unit tests are not my forte... If anyone can help, that would be great.


Solution

  • I answered my own questions... Posting in case somebody else runs into the same issue.

    The error was complaining about HTMLCanvasElement. It turns out the component I was trying to stub out was using the Canvas library itself which wasn't required when running in the browser, but since I was building a test, I just added the Canvas library to my package and the issue was solved. Full code below:

    import React from 'react';
    import { render, cleanup } from '@testing-library/react';
    import { expect, assert } from 'chai';
    import * as lottie from 'lottie-react-web';
    import { createSandbox } from 'sinon';
    
    import LoadingOverlay from '../src/components/LoadingOverlay';
    
    // test ID
    const TEST_ID = 'the-test-id';
    
    // mocks
    const sandbox = createSandbox();
    const MockLottie = () => 'Mock Lottie';
    
    describe('Loading Overlay', () => {
      beforeEach(() => {
        sandbox.stub(lottie, 'default').callsFake(MockLottie);
      });
    
      afterEach(() => {
        sandbox.restore();
        cleanup();
      });
    
      it('should have test ID', () => {
        const { getByTestId } = render(
          <LoadingOverlay testId={TEST_ID} size="medium" />
        );
        expect(getByTestId(TEST_ID)).to.not.equal(null);
      });
    });