使用简单的 jest 函数模拟 makeStyles 会使您失去测试覆盖率。当它变得更复杂时,它会导致一些问题,并且每个解决的问题都会导致另一个问题:
- 在调用 useStyles 时失去了测试覆盖率,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));
},
};
});
从那以后,我也学会了mockuseEffect和调用callback、axios全局拦截器等。