0

I have a parent component called DismissButton:

    import React, { useContext } from 'react';
    import PropTypes from 'prop-types';
    import { StyledDismissButton } from './Styled';
    import { IoClose } from 'react-icons/io5';
    import VisibilityContext from '../../context';
    
    export const DismissButton = () => {
       const { setVisible } = useContext(VisibilityContext);
       const hideToast = () => setVisible(false);
       return (
          <StyledDismissButton aria-label="Close" onClick={hideToast}>
             <IoClose />
          </StyledDismissButton>
       );
    };
    
    StyledDismissButton.propTypes = {
       onClick: PropTypes.func.isRequired,
    };
    
    export { StyledDismissButton };

It consumes this context:


    import { createContext } from 'react';
    
    const initialState = {
       visible: true,
       setVisible: () => {},
    };
    const VisibilityContext = createContext(initialState);
    
    export default VisibilityContext;

It holds a child component StyledDissmissButton:


import styled from "styled-components";
const StyledDismissButton = styled.span`
  right: 0;
  cursor: pointer;
`;

export { StyledDismissButton };

I want to test DismissButton with Enzyme and Jest but I failed each time because the component consumes a context. I've tried this soltion but it does not work for me.

Test


import 'jsdom-global/register';
import React from 'react';
import Enzyme, { shallow } from 'enzyme';
import Adapter from '@wojtekmaj/enzyme-adapter-react-17';
import DismissButton, { StyledDismissButton } from './index';
import { IoClose } from 'react-icons/io';
Enzyme.configure({ adapter: new Adapter() });

it('renders one <StyledDissmisButton> when passed in', () => {
   const contextValue = {
      visible: true,
      setVisible: () => {},
      hideToast() {
         this.setVisible(false);
      },
   };

   const element = shallow(<DismissButton />);
   expect(
      element.contains(
         <StyledDismissButton aria-label="Close" onClick={contextValue.hideToast}>
            <IoClose />
         </StyledDismissButton>
      )
   ).toEqual(true);
});

Test Result

 expect(received).toEqual(expected)
    
    Expected value to equal:
      true
    Received:
      false
      
      at Object.<anonymous> (src/components/Toast/Header/DismissButton/index.test.js:32:7)
          at new Promise (<anonymous>)
      at node_modules/p-map/index.js:46:16
      at processTicksAndRejections (node:internal/process/task_queues:96:5)

Test Suites: 1 failed, 1 passed, 2 total
Tests:       1 failed, 4 passed, 5 total
Snapshots:   1 passed, 1 total
Time:        13.773s

You can check the repos on React Toast

Debugging result


    <TestComponent>
            <Component>
              <styled.span aria-label="Close" onClick={[Function: hideToast]}>
                <span aria-label="Close" onClick={[Function: hideToast]} className="sc-bdnxRM gsGVlo">
                  <IoClose>
                    <IconBase attr={{...}}>
                      <svg stroke="currentColor" fill="currentColor" strokeWidth="0" viewBox="0 0 512 512" className={[undefined]} style={{...}} height="1em" width="1em" xmlns="http://www.w3.org/2000/svg">
                        <path d="M289.94 256l95-95A24 24 0 00351 127l-95 95-95-95a24 24 0 00-34 34l95 95-95 95a24 24 0 1034 34l95-95 95 95a24 24 0 0034-34z" />
                      </svg>
                    </IconBase>
                  </IoClose>
                </span>
              </styled.span>
            </Component>
   </TestComponent>

Update

After a deep focusing I've found that Styled Component generates add a class to the component which makes the matching impossible:

<span aria-label="Close" onClick={[Function: hideToast]} className="sc-bdnxRM gsGVlo">

But I don't know how to pass that class in the test?

  • Could you please provide more context as to what actually gets rendered in your test case? Perhaps the output of something like `wrapper.debug()` would tell me more, my guess would be that the onClick method you are checking in `wrapper.contains()` doesn't match the one in the component since that one is inlined, while the one you are comparing to is statically defined in the context. – Filip Kaštovský Jul 16 '21 at 19:23
  • 1
    @FilipKaštovský, you can check [the repo](https://codesandbox.io/s/5kfz9). – Menai Ala Eddine - Aladdin Jul 16 '21 at 20:09

1 Answers1

0

The reason the test fails is because functions hideToast created in the scope of the component and hideToast defined in the test are referentially different. When wrapper.contains checks the onClick props on StyledDismissButton, it fails, since the two functions are not the same.

One way to go around this is to implement hideToast in the context itself and use it as provided by the useContext hook.

const initialState = {
   visible: true,
   setVisible: () => {},
   hideToast(){
      this.setVisible(false)
   }
};
export const DismissButton = () => {
   const { hideToast } = useContext(VisibilityContext);

   return (
      <StyledDismissButton aria-label="Close" onClick={hideToast}>
         <IoClose />
      </StyledDismissButton>
   );
};

Props now match, the test passes.

describe('<DismissButton/>', () => {
   it('renders one <StyledDissmisButton> when passed in', () => {
      const contextValue = {
         visible: true,
         setVisible: () => {},
         hideToast(){
            this.setVisible(false)
         }
      };
 
      const TestComponent = () => {
         return (
            <Context.Provider value={contextValue}>
               <DismissButton />
            </Context.Provider>
         );
      };
      const element = mount(<TestComponent />);
   
      expect(
         element.contains(
            <StyledDismissButton aria-label="Close" onClick={contextValue.hideToast}>
               <IoClose />
            </StyledDismissButton>
         )
      ).toEqual(true);
   });
});

Codesandbox fork

Filip Kaštovský
  • 1,866
  • 8
  • 12
  • Stir getting the same error, could you run the test on the giving repo on sandbox. – Menai Ala Eddine - Aladdin Jul 16 '21 at 20:58
  • I was having issues with devdependencies on the sandbox, so I cloned the project locally and debugged it there, but I will try to make it work in the sandbox as well – Filip Kaštovský Jul 16 '21 at 21:08
  • I've updated the answer to include the working codesandbox fork – Filip Kaštovský Jul 16 '21 at 21:24
  • In the original repo there was a typo in this import in `index.test.js`: `import { IoClose } from "react-icons/io";`, should be `import { IoClose } from "react-icons/io5";` that made the test run correctly. My IDE lit that up but i forgot to reflect that change in the sandbox until now – Filip Kaštovský Jul 16 '21 at 21:26
  • Also I would like to point out that there are some situations where the answer might not be applicable. in my experience, it is better to assert on different things then if a react subtree is contained in the wrapper, and the question problem is one of the reasons why. I used html snapshot testing with enzyme instead for a while, since I found it way more straight forward and was really only concerned about the final result of the render (after simulating a few events to get to the desired state). Unit tests mostly covered any internal logic and I found no benefit in testing react itself. – Filip Kaštovský Jul 16 '21 at 21:30