如何模拟和测试MaterialUI-makeStyles

时间:2020-11-01 05:19:12

标签: reactjs unit-testing jestjs material-ui enzyme

我正在尝试为我的React组件添加更好的测试范围,而我无法嘲笑的地方之一就是它的内部

export const useTabStyles = makeStyles(({ options: { common } }) => ({
>>>  root: ({ size }: TabProps) => ({
    '&&': {
      fontSize: size === 'MD' ? common.fonts.sizes.p3 : common.fonts.sizes.p,
    },
  }),
}));

当我检查代码覆盖率时,是说未检查>>>行。 我尝试过这样的事情

jest.mock('@material-ui/core/styles', () => ({
  ...jest.requireActual('@material-ui/core/styles'),
  makeStyles: jest.fn().mockReturnValue(jest.fn()),
}));

但是我不确定如何检查给定的行是否用size = MD or LG调用。

这是it

的代码
it('should render normal style', () => {
    wrapper = shallow(<Tab size="MD" />);
    // how do I mock check here whtehr the makeStyles received the proepr size.
  });

4 个答案:

答案 0 :(得分:0)

将其提取到函数中并分别进行测试是什么?

答案 1 :(得分:0)

覆盖范围方面正在发生的事情是正在测试的函数,钩子useTabStylesmakeStyles fn的结果,它接受一个回调作为输入,这是缺少覆盖范围的回调,因为它不会在您的模拟之后执行。

如果您以这种方式更改了模拟程序,则还应该执行该代码,然后将其覆盖:

makeStyles: jest.fn().mockImplementation(callback => {
  callback({ options: { common: { fonts: { sizes: {} } } } }); // this will execute the fn passed in which is missing the coverage
  return jest.fn().mockReturnValue({ // here the expected MUI styles });
}),

您也可以直接在以下行之前添加该fn的覆盖范围检查:

/* istanbul ignore next */
export const useTabStyles = makeStyles(({ options: { common } }) => ({
  root: ({ size }: TabProps) => ({
    '&&': {
      fontSize: size === 'MD' ? common.fonts.sizes.p3 : common.fonts.sizes.p,
    },
  }),
}));

答案 2 :(得分:0)

用简单的玩笑函数模拟 makeStyles 会让你失去测试覆盖率。当它变得更复杂时,它会导致一些问题,每个解决的问题都会导致另一个问题:

  • 在调用 useStyles 时它失去了测试覆盖率,它现在是一个没有样式的空函数 (const useStyles = makeStyles(theme => {...}))
  • 不模拟它会为自定义主题的附加值引发错误
  • 将模拟函数参数与自定义主题绑定有效,您可以调用函数参数来填充覆盖范围。但是,如果在调用 useStyles({ variant: 'contained', palette: 'secondary' })(makeStyles 的结果函数)时传递参数,则会丢失覆盖率
  • 在模拟 useContext 时,很多事情都失败了,因为 makeStyles 结果函数在内部使用了 useContext。

(useStyles 参数处理示例)

{
  backgroundColor: props => {
    if (props.variant === 'contained') {
      return theme.palette[props.palette].main;
    }
    return 'unset';
  },
}

我设法解决了所有这些问题并使用了手动模拟 https://jestjs.io/docs/en/manual-mocks

第 1 步:

我在核心路径中进行了模拟,但两者都应该有效:<root>/__mocks__/@material-ui/core/styles.js

// Grab the original exports
// eslint-disable-next-line import/no-extraneous-dependencies
import * as Styles from '@material-ui/core/styles';
import createMuiTheme from '@material-ui/core/styles/createMuiTheme';
import options from '../../../src/themes/options'; // I put the theme options separately to be reusable

const makeStyles = func => {
  /**
   * Note: if you want to mock this return value to be
   * different within a test suite then use
   * the pattern defined here:
   * https://jestjs.io/docs/en/manual-mocks
   */

  /**
   * Work around because Shallow rendering does not
   * Hook context and some other hook features.
   * `makeStyles` accept a function as argument (func)
   * and that function accept a theme as argument
   * so we can take that same function, passing it as
   * parameter to the original makeStyles and
   * bind it to our custom theme, created on the go
   *  so that createMuiTheme can be ready
   */
  const theme = createMuiTheme(options);
  return Styles.makeStyles(func.bind(null, theme));
};

module.exports = { ...Styles, makeStyles };

所以基本上,这只是使用相同的原始 makeStyles 并将未按时准备好的自定义主题传递给它。

第 2 步:

makeStyles 结果使用 React.useContext,因此我们必须避免在 makeStyles 用例中模拟 useContext。如果您在组件中首先使用 React.useContext(...),请使用 mockImplementationOnce,或者最好在测试代码中将其过滤为:

jest.spyOn(React, 'useContext').mockImplementation(context => {
  // only stub the response if it is one of your Context
  if (context.displayName === 'MyAppContext') {
    return {
      auth: {},
      lang: 'en',
      snackbar: () => {},
    };
  }

  // continue to use original useContext for the rest use cases
  const ActualReact = jest.requireActual('react');
  return ActualReact.useContext(context);
});

在您的 createContext() 调用中,可能在 store.js 中,添加 displayName 属性(标准)或任何其他自定义属性来识别您的上下文:

const store = React.createContext(initialState);
store.displayName = 'MyAppContext';

如果您记录它们,makeStyles 上下文 displayName 将显示为 StylesContext 和 ThemeContext,并且它们的实现将保持不变以避免错误。

这修复了所有与 makeStyles + useContext 相关的模拟问题。在速度方面,感觉就像正常的 shallow 渲染速度,并且在大多数用例中可以让您远离 mount

步骤 1 的替代方法:

我们可以在任何测试中使用普通的 jest.mock,而不是全局手动模拟。这是实现:

jest.mock('@material-ui/core/styles', () => {
  const Styles = jest.requireActual('@material-ui/core/styles');

  const createMuiTheme = jest.requireActual(
    '@material-ui/core/styles/createMuiTheme'
  ).default;

  const options = jest.requireActual('../../../src/themes/options').default;

  return {
    ...Styles,
    makeStyles: func => {
      const theme = createMuiTheme(options);
      return Styles.makeStyles(func.bind(null, theme));
    },
  };
});

从那以后,我也学会了模拟 useEffect 和调用回调、axios 全局拦截器等

答案 3 :(得分:0)

我遇到了同样的问题,所以我就这样解决了这个问题,希望对其他人也有帮助。

谢谢

这是我的样式文件

import { makeStyles } from '@material-ui/core/styles';

export const useStyles = makeStyles((theme) => ({
   root:{
       backgroundColor: theme.common.white,
   }
}));

这是我的组件

import { useStyles } from './ExampleStyles';
const Example = ({ children }) => {
    const classes = useStyles();
    return (
       <div className={classes.root}><h4>Hello world!</h4></div>
    );
};
export default Example;

现在是测试用例。

import { ThemeProvider } from '@material-ui/core/styles';
import Adapter from '@wojtekmaj/enzyme-adapter-react-17';
import Enzyme, { mount } from 'enzyme';
import renderer from 'react-test-renderer';
import Example from 'shared/components/Example';
import theme from 'shared/utils/theme';
Enzyme.configure({ adapter: new Adapter() });

describe('Example Component', () => {
    const props = {};
    it('Should render Example component', () => {
        const wrapper = mount(
            <ThemeProvider theme={theme}>
                <Example {...props} />
            </ThemeProvider>
        );
        expect(wrapper).toBeTruthy();
    });

    it('Example Component snapshot testing', () => {
        const div = document.createElement('div');
        const tree = renderer
            .create(
                <ThemeProvider theme={theme}>
                    <Example {...props} />
                </ThemeProvider>,
                div
            )
            .toJSON();
        expect(tree).toMatchSnapshot();
    });
});

我只是使用 ThemeProvider 来访问 html 中的主题变量