Search code examples
reactjstypescriptunit-testingjestjs

Write unit test case for React functional component using JEST


I am new to unit test cases, My project uses typescript with React. I would appreciate some help in writing the test cases for the following code for one of my components. I have been trying to write a test case to satisfy the two functions here getLogo and setupRegistry.Not being able to figure out the correct way to do that -

    import {
    getMediaToken,
    isManifestRegistryItem,
    MediaRegistryHelper,
    RegistryItem,
    SupportedRegistryMedia,
    SvgUtils,
  } from 'common';
  import React, {
    FunctionComponent,
    useEffect,
    useState,
    useContext,
  } from 'react';
  import { Observable, of } from 'rxjs';
  import { filter, map } from 'rxjs/operators';
  import { downloadMedia, loadMediaRegistry } from '../media-registry';
  import { LogoAssetContext } from '../media-context';
  
  export interface RBCLogoProperties {
    token: string;
    srLabel: string;
  }
  
  type RBCLogoType = RBCLogoProperties & React.HTMLAttributes<HTMLLabelElement>;
  export const RCLogo: FunctionComponent<RBCLogoType> = (props: RBCLogoType) => {
    const { token, srLabel } = props;
    const LOGOASSETREGISTRY = useContext(LogoAssetContext);
  
    useEffect(() => {
      setupRegistry(
        LOGOASSETREGISTRY as Array<RegistryItem<SupportedRegistryMedia>>,
      );
    });
  
    // Media Helper Instance to access the methods defined in media helper
  
    const mediaHelperInstance = new MediaRegistryHelper();
  
    // State to set the svg to html to show the logo
  
    const [svg, setSVG] = useState<string>();
  
    //Set up the registry for logo assets
  
    const setupRegistry = (
      registryConfig: Array<RegistryItem<SupportedRegistryMedia>>,
    ) => {
      let config: Array<RegistryItem<SupportedRegistryMedia>> = [];
      if (registryConfig && registryConfig.length > 0) {
        const items = registryConfig;
        config = [...config, ...items];
      }
      loadMediaRegistry?.('logo', config).subscribe(() => getLogo());
    };
  
    const getLogo = () => {
      if (token) {
        const logo = getMediaToken(token);
        const registryItem = mediaHelperInstance?.getRegistryMediaItem(
          'logo',
          logo.scope || 'default',
        );
        let item$: Observable<RegistryItem<SupportedRegistryMedia> | undefined> =
          of(registryItem);
  
        if (isManifestRegistryItem(registryItem)) {
          item$ = registryItem.data.pipe(
            filter((v) => !!v),
            map(() => registryItem),
          );
        }
        const downLoadMediaResponse = downloadMedia('logo', logo);
        downLoadMediaResponse
          ? downLoadMediaResponse.subscribe((data: any) => {
              const svg = data as SVGElement;
              setSVG(svg.outerHTML);
              SvgUtils.setSvgAttributes(svg);
              SvgUtils.addA11ytoSVG(
                svg,
                token,
                srLabel ? srLabel : ' ',
                document,
              );
            })
          : console.log('Download Media Response is undefined for', token);
      }
    };
  
    return (
      <div
        aria-label={srLabel}
        dangerouslySetInnerHTML={{ __html: svg || '' }}
      ></div>
    );
  };
  
  export default RCLogo;
  

Solution

  • Regarding unit testing of React components, you typically test the whole unit in terms of the component. You won't be able to test getLogo and setupRegistry (I assume it is what you have meant by setSubscription? If not, you need to add a snippet for that function).

    You can't test those specific functions because they are encapsulated within your component, and the test runner doesn't have access to them. More importantly, the test runner shouldn't have access to them since that would reveal the implementation detail of the component, and tests should be agnostic of that.

    There is, however, a limited way to test functions inside the component, but only if the function is declared elsewhere and imported to the component. You can then use spy to observe if the spied method has been called with what and how many times. Again you would use it only in specific cases (say you need to test that analytics event within the component has been only dispatched once, even if the component re-render etc..)

    So, if I were to write a unit test for this component, I would have the following test cases:

    1. Is the component returning the expected svg logo element?
    2. Is it properly aria labeled?

    I would not test getLogo or any other encapsulated function because you care about the final SVG output.

    So, you need three things from the code example you provided before testing this component.

    1. Mock the API response with the expected data (you can use Jest nock)
    2. Wrap your component into the LogoAssetContext provider so the component can access it.
    3. Provide correct props to the component.

    There is also an argument about how much value the unit test brings to presentational components such as the logo that don't do anything other than display the image/svg.

    Also, I would try to avoid using dangerously set HTML to render the SVG. Since it is returned as a string but also from the API call, I wouldn't trust the information returned from elsewhere to be used to set inner HTML. It exposes you to XSS attacks if svg is not actual svg.

    I would use something like the below inside your div.

    <img src={`data:image/svg+xml;utf8,${svg}`} />