"Encountering an issue with mounting components in React Unit Testing with Jest and Typescript

Having developed a simple app with components, here is the code:

import GraphicCanvas from './Graphing/GraphCanvas';
import { drawCircle } from './Graphing/DrawCircle';
function App() {
  return (
    <div className="App">
     < GraphicCanvas  draw={drawCircle} />
    </div>
  );
}

export default App;

    import React, { FC, useRef, useEffect } from "react";
    
    export interface IGraphicCanvas{
        draw : Function;
    }
    
    const GraphicCanvas : FC<IGraphicCanvas> = (props) : JSX.Element => {
        const canvasRef = useRef<HTMLCanvasElement>(null)
    
        useEffect(() => {
            if (canvasRef.current) {
                const canvas = canvasRef.current
                const context = canvas?.getContext('2d')
                props.draw(context)
            }
        }, [props.draw])
    return <canvas ref={canvasRef} />

}

export default GraphicCanvas ;

export const drawCircle = (ctx : CanvasRenderingContext2D | null ) : void => {
    ctx!.fillStyle = '#000000'
    ctx?.beginPath()
    ctx?.arc(50, 100, 20, 0, 2 * Math.PI)
    ctx?.fill()
}

The functionality in the code works as expected and produces a small black dot on the canvas. However, running unit tests using Enzyme and Jest, specifically the mount() function, results in errors.

Shown below is the test script intended to validate the drawCircle function passed as a prop to GraphicCanvas:

import React from 'react'
import { configure, shallow, mount } from "enzyme";
import Adapter from "enzyme-adapter-react-16";

import GraphicCanvas, { IGraphicCanvas } from "./GraphCanvas";
import  { drawCircle }  from "./DrawCircle"

configure({ adapter: new Adapter() });

describe("GraphCanvas ", () => {
  
  let props: IGraphicCanvas;
  let useEffect: { getMockImplementation: (arg0: (ctx: CanvasRenderingContext2D | null) => void) => void; }
  
  const mockUseEffect = () => {
    useEffect.getMockImplementation( drawCircle );
  };

  beforeEach(() => {
    props = { draw: jest.fn().getMockImplementation }; 
  });

  //shallow method renders only a single component, without child components.
  it("renders without crashing", () => {
    shallow(<GraphicCanvas draw={drawCircle} />);
  });

  it("calls function drawCircle passed in props as draw", () => {
    //mockUseEffect();
    //mount(<GraphicCanvas draw={drawCircle} />);
    mount(<GraphicCanvas {...props} />);
    expect(props.draw).toHaveBeenCalled();
  });

});

An error message is encountered when attempting to run the test, indicating an issue with line 31 of the code:

TypeError: Cannot read properties of undefined (reading 'child')

      29 |     //mockUseEffect();
      30 |     //mount(<GraphicCanvas draw={drawCircle} />);
    > 31 |     mount(<GraphicCanvas {...props} />);
         |     ^
      32 |     expect(props.draw).toHaveBeenCalled();
      33 |   });
      34 |

Multiple attempts are made by uncommenting different lines, all resulting in various error messages. The objective remains to simply ensure that the drawCircle function passed through the prop "draw" to GraphicCanvas is being called.

 expect(props.draw).toHaveBeenCalled()

Answer №1

There's no need to ridicule the useEffect() hook. It's best to stick with the original version to ensure the component behaves as expected, rather than focusing on testing implementation details that can make tests fragile and prone to failure with even minor changes. Incorrectly mocking can also lead to inaccurate results, where tests pass but the code fails at runtime.

The main focus should be on the behavior and functionality of the component, not intricate implementation specifics.

GraphqCanvas.tsx:

import React, { FC, useRef, useEffect } from 'react';

export interface IGraphicCanvas {
  draw: Function;
}

const GraphicCanvas: FC<IGraphicCanvas> = (props): JSX.Element => {
  const canvasRef = useRef<HTMLCanvasElement>(null);

  useEffect(() => {
    if (canvasRef.current) {
      const canvas = canvasRef.current;
      const context = canvas?.getContext('2d');
      props.draw(context);
    }
  }, [props.draw]);
  
  return <canvas ref={canvasRef} />;
};

export default GraphicCanvas;

GraphicCanvas.test.tsx:

import GraphicCanvas, { IGraphicCanvas } from './GraphCanvas';
import { configure, shallow, mount } from 'enzyme';
import Adapter from 'enzyme-adapter-react-16';
import React from 'react';
configure({ adapter: new Adapter() });

describe('GraphicCanvas', () => {
  let props: IGraphicCanvas;
  
  beforeEach(() => {
    props = { draw: jest.fn() };
  });
  
  it('calls function drawCircle passed in props as draw', () => {
    HTMLCanvasElement.prototype.getContext = jest.fn();
    mount(<GraphicCanvas {...props} />);
    expect(props.draw).toHaveBeenCalled();
  });
});

Test Result:

 PASS  examples/69780222/GraphCanvas.test.tsx (10.389 s)
  GraphicCanvas
    ✓ calls function drawCircle passed in props as draw (35 ms)

-----------------|---------|----------|---------|---------|-------------------
File             | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s 
-----------------|---------|----------|---------|---------|-------------------
All files        |     100 |    66.67 |     100 |     100 |                   
 GraphCanvas.tsx |     100 |    66.67 |     100 |     100 | 11-13             
-----------------|---------|----------|---------|---------|-------------------
Test Suites: 1 passed, 1 total
Tests:       1 passed, 1 total
Snapshots:   0 total
Time:        11.08 s
Ran all test suites related to changed files.

Package Versions:

"enzyme": "^3.11.0",
"enzyme-adapter-react-16": "^1.15.5",
"jest": "^26.6.3",
"react": "^16.14.0",

Answer №2

Enzyme is having trouble with React version 17, causing the mount function to fail. Luckily, there is an unofficial adapter available for v17 under the name @wojtekmaj/enzyme-adapter-react-17.

Here is a simple test that I have created:

import GraphicCanvas, { IGraphicCanvas } from './GraphCanvas';
import { configure, mount } from "enzyme";
import Adapter from '@wojtekmaj/enzyme-adapter-react-17'

configure({ adapter: new Adapter() });

describe('GraphicCanvas', () => {
  let props: IGraphicCanvas;
  beforeEach(() => {
    props = { draw: jest.fn() };
  });
  it('calls function drawCircle passed in props as draw', () => {
      HTMLCanvasElement.prototype.getContext = jest.fn();
      mount(<GraphicCanvas {...props} />);
      expect(props.draw).toHaveBeenCalled();
  });
});

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

React-Redux: Unable to access the 'closed' property as it is undefined

Encountered a problem when using dispatch() in React-Redux. Specifically, the action below: export const fetchMetrics = () => { dispatch(fetchMetricsBegin); APIService.get('/dashboard/info/') .then((response) => { ...

Theme not being rendered properly following the generation of a dynamic component in Angular

I am currently working on an Angular 9 application and I have successfully implemented a print functionality by creating components dynamically. However, I have encountered an issue where the CSS properties defined in the print-report.component.scss file a ...

The function getServerSideProps does not return any value

I'm a beginner with Next.js and I'm currently using getServerSideProps to retrieve an array of objects. This array is fetched from a backend API by utilizing the page parameters as explained in the dynamic routes documentation: https://nextjs.org ...

Looking for a way to ensure that the useEffect hook only runs once when the DOM is rendered in Next.js? Or perhaps you have a more efficient

Currently, I am facing an issue where a function in the lib folder that fetches server data is being called twice due to useEffect, resulting in unwanted output. How can I resolve this problem specifically in Next.js? I have come across some solutions fo ...

There was an error encountered trying to access the options (URL) with a 405 method not allowed status. Additionally, the request to load the (URL) failed with a response indicating an

I attempted to retrieve data from an API using httpClient in Angular 5, but encountered errors. Below are the issues I faced: 1) ERROR: when trying to access http://localhost:8080/api/getdata, I received a 405 error (method not allowed). 2) ERROR: failed t ...

Using conditional CSS in React/Next.js

While using Next.js, I have implemented conditional rendering on components successfully. However, I am facing an issue where the CSS styles differ between different components. Code 1: import React from "react"; import Profile from "../../ ...

Event-Propagation in Angular 5 with mat-expansion-panel within another component

In my project, I am facing a challenge where I need to create multiple mat-expansion-panels within one mat-expansion-panel. Everything works fine except for the issue that when I try to open a child-panel, it triggers the close-event of the parent-panel. ...

Outdated state variable value recorded in the log

I am facing an issue where the logged value of the state variable 'count' is always zero, even after clicking the button to update it. The logging function is triggered by a setInterval function within the useEffect hook. Is there a reason why t ...

What reasons underlie the existence of various methods for importing Modules in JavaScript?

I'm confused about the distinctions when it comes to importing a JavaScript module in various ways such as: CommonJS ES5 ES6 NodeJS Typescript What is the reason for having multiple methods of importing JavaScript modules? Is the concept of a "modu ...

Trigger a dispatched action within an NGRX selector

I want to ensure that the data in the store is both loaded and matches the router parameters. Since the router serves as the "source of truth," I plan on sending an action to fetch the data if it hasn't been loaded yet. Is it acceptable to perform the ...

Pagination in Material-UI Datagrid seems to be starting on the second page instead of the first when the page number is

I am encountering an issue with the pagination on my Material-UI Datagrid. When I make a request to an API with specified page numbers and limits (e.g. `page=${page}&limit=10`), I receive 10 rows of data along with any pagination information sent by La ...

Outputting messages to a component with React

I'm attempting to create a component similar to a console where messages are displayed one after the other instead of replacing the old message. My goal is to have a component where I can input strings, like in a chatbox, using different parts of my ...

One way to incorporate type annotations into your onChange and onClick functions in TypeScript when working with React is by specifying the expected

Recently, I created a component type Properties = { label: string, autoFocus: boolean, onClick: (e: React.ClickEvent<HTMLInputElement>) => void, onChange: (e: React.ChangeEvent<HTMLInputElement>) => void } const InputField = ({ h ...

The specified property is not found in the type 'IntrinsicAttributes & IntrinsicClassAttributes<DatePicker> & Readonly<{ children?: ReactNode; }>'

As I delve into utilizing React along with TypeScript and Material-UI components, I encounter some errors. One such error message pops up like this: The Property 'openToYearSelection' is not found on type 'IntrinsicAttributes & Intr ...

What is the best way to declare a variable while rendering HTML in a ReactJS loop?

render() { return ( <ul> {scopeOptions.map((option, keyOption) => ( const myVar = keyOptions * 5 <li key={keyOption}> <a href="#" data-value={option.value}>{option.label}</a> </li> ))} </ul> ) } I ...

Issue encountered while using Material-UI makeStyles: unable to access property 'down' due to being undefined

I'm currently in the process of developing my own website using Material-UI and I'm facing an issue with making it responsive. My goal is to hide an image on smaller screens by utilizing [theme.breakpoints.down('md')]. However, I keep e ...

Having issues with Material UI position sticky in my Next JS project - help needed

<Box width={'30%'} > <Box sx={{ position: 'sticky', top: '100px', transition: 'all .4s ease' }}> {/* My render elements */} </Box> </Box> I am currently attempting to troubles ...

How can you activate a prop in react native based on a certain condition?

To add a border to a native base button only if the menu index is equal to the house menu, use prop bordered. <Button bordered> <Text uppercase={false}>House</Text> </Button> My attempt: <Button {menuIndex == menus.house? ...

Referring to TypeScript modules

Consider this TypeScript code snippet: module animals { export class Animal { } } module display.animals { export class DisplayAnimal extends animals.Animal { } } The objective here is to create a subclass called DisplayAnimal in the dis ...

Tips for avoiding the influence of the parent div's opacity on child divs within a Primeng Carousel

I'm struggling to find a solution to stop the "opacity" effect of the parent container from affecting the child containers. In my code, I want the opacity not to impact the buttons within the elements. I have tried using "radial-gradient" for multipl ...