Search code examples
reactjstestingreact-hooksreact-hooks-testing-library

Test react hooks state using Jest and React Hooks Library


I nav component then will toggle state in a sidebar as well as open and close a menu and then trying to get this pass in code coverage. When I log inside my test my state keeps showing up as undefined. Not sure how to tackle this one here.

Component.js:

const Navigation = (props) => {
  const {
    classes,
    ...navProps
  } = props;

  const [anchorEl, setanchorEl] = useState(null);
  const [sidebarOpen, setsidebarOpen] = useState(false);

  const toggleSidebar = () => {
    setsidebarOpen(!sidebarOpen);
  };

  const toggleMenuClose = () => {
    setanchorEl(null);
  };

  const toggleMenuOpen = (event) => {
    setanchorEl(event.currentTarget);
  };

  return (
    <Fragment>
     <Button
          onClick={toggleMenuOpen}
        />
      <SideMenu
        toggleSidebar={toggleSidebar}
      >
       <Menu
          onClose={toggleMenuClose}
        >
      </SideMenu>
    </Fragment>
  );
};

export default Navigation;

Test.js:

import { renderHook, act } from '@testing-library/react-hooks';

// Components
import Navigation from './navigation';

test('sidebar should be closed by default', () => {
    const newProps = {
        valid: true,
        classes: {}
    };

    const { result } = renderHook(() => Navigation({ ...newProps }));
    expect(result.current.sidebarOpen).toBeFalsy();
});

Solution

  • Author of react-hooks-testing-library here.

    react-hooks-testing-library is not for testing components and interrogating the internal hook state to assert their values, but rather for testing custom react hooks and interacting withe the result of your hook to ensure it behaves how you expect. For example, if you wanted to extract a useMenuToggle hook that looked something like:

    export function useMenuToggle() {
      const [anchorEl, setanchorEl] = useState(null);
      const [sidebarOpen, setsidebarOpen] = useState(false);
    
      const toggleSidebar = () => {
        setsidebarOpen(!sidebarOpen);
      };
    
      const toggleMenuClose = () => {
        setanchorEl(null);
      };
    
      const toggleMenuOpen = (event) => {
        setanchorEl(event.currentTarget);
      };
    
      return {
        sidebarOpen,
        toggleSidebar,
        toggleMenuClose,
        toggleMenuOpen
      }
    }
    

    Then you could test it with renderHook:

    import { renderHook, act } from '@testing-library/react-hooks';
    
    // Hooks
    import { useMenuToggle } from './navigation';
    
    test('sidebar should be closed by default', () => {
        const newProps = {
            valid: true,
            classes: {}
        };
    
        const { result } = renderHook(() => useMenuToggle());
        expect(result.current.sidebarOpen).toBeFalsy();
    
        act(() => {
            result.current.toggleSidebar()
        })
    
        expect(result.current.sidebarOpen).toBeTruthy();
    });
    

    Generally though, when a hook is only used by a single component and/or in a single context, we recommend you simply test the component and allow the hook to be tested through it.

    For testing your Navigation component, you should take a look at react-testing-library instead.

    import React from 'react';
    import { render } from '@testing-library/react';
    
    // Components
    import Navigation from './navigation';
    
    test('sidebar should be closed by default', () => {
        const newProps = {
            valid: true,
            classes: {}
        };
    
        const { getByText } = render(<Navigation {...newProps} />);
        
        // the rest of the test
    });