reactjsmaterial-uienzyme

Trying to mock MUI's useMediaQuery hook behaviour in Enzyme test


EDIT: I was able to determine that MUI's instructions work correctly when using RTL. This issue is only taking place in Enzyme tests!

I'm following MUI's documentation on how to test useMediaQuery, but I am confused as to whether or not the way I am using useMediaQuery (outlined here in MUI's docs) in my component is compatible with the testing instructions in MUI's docs.

Here's the code in my component:

import { useTheme } from '@mui/material/styles';
import useMediaQuery from '@material-ui/core/useMediaQuery';

  const List = () => {
      const theme = useTheme();
      const isDownLargeBreakpoint = 
      useMediaQuery(theme.breakpoints.down('lg'));

      ...
      
      {isDownLargeBreakpoint && (
          <ul className="list">
            // list items
          </ul>
      )}

  }

The useMediaQuery hook works as expected when I run my app locally, it correctly toggles between true and false when I resize the screen below/above MUI's theme lg breakpoint.

When I try to run my test with the recommended method of setup, despite the window.innerWidth falling below what would satisfy useMediaQuery to return a value of true I always get false in my test. Perhaps it's because I'm not rerendering my component from within my test? Or do I have to do something more in my it clause to trigger what is needing to happen?

Here's the block of code using css-mediaquery recommended by MUI as well as this post which was already answered:

import mediaQuery from 'css-mediaquery';

function createMatchMedia(width) {
  return (query) => ({
    matches: mediaQuery.match(query, {
      width,
    }),
    addListener: () => {},
    removeListener: () => {},
  });
}

describe('MyTests', () => {
  beforeAll(() => {
    window.matchMedia = createMatchMedia(window.innerWidth);
  });
});

Here's how I've organized my test file:

import React from 'react';
import { shallow } from 'enzyme';
import mediaQuery from 'css-mediaquery';

import SessionStore from 'app/stores/SessionStore';

import CustomFields from '../CustomFields';
import CustomFieldButton from '../CustomFieldButton';

import PrepareFieldsList from '../PrepareFieldsList';

describe('PrepareFieldsList Component', () => {
  let wrapper;

  function createMatchMedia(width) {
    return (query) => ({
      matches: mediaQuery.match(query, {
        width,
      }),
      addListener: () => {},
      removeListener: () => {},
    });
  }

  beforeAll(() => {
    window.matchMedia = createMatchMedia(window.innerWidth);
  });

  const defaultProps = {
    customFields: [
      {
        data: null,
        id: 'fieldId',
        name: '',
        required: false,
        value: 'test',
      },
    ],
  };

  beforeEach(() => {
    jest.spyOn(SessionStore, 'getSession').mockReturnValue({
      hasFeature: () => true,
    });
    wrapper = shallow(<PrepareFieldsList {...defaultProps} />);
  });

  ...

  it('should render CustomFieldButton and CustomFields when hasFeature is true', () => {
    expect(wrapper.find(CustomFieldButton)).toHaveLength(1);
    expect(wrapper.find(CustomFields)).toHaveLength(1);
  });
});


Solution

  • I figured it out. When doing this in Enzyme, you need to mock the hook and its return value with jest.

    At the top of the test file:

    import useMediaQuery from '@material-ui/core/useMediaQuery';
    

    The mock at the top of the file, below the imports:

    jest.mock('@material-ui/core/useMediaQuery');
    

    Then to update the mock value:

    useMediaQuery.mockReturnValueOnce(true);
    

    Make sure to do this before you render your component in each test case. So like:

    it('should render ChildComponent when useMediaQuery is true', () => {
        useMediaQuery.mockReturnValueOnce(true);
        const wrapper = shallow(<ParentComponent />);
        expect(wrapper).toContainExactlyOneMatchingElement(ChildComponent);
    });