如何使用react-testing-library测试包装在withStyles中的样式化Material-UI组件?

May*_*ate 11 typescript reactjs jestjs material-ui react-testing-library

我正在尝试使用打字稿中的react-testing-library创建带有样式的Material-UI组件的测试。我发现很难访问组件的内部功能以进行模拟和断言。

Form.tsx

export const styles = ({ palette, spacing }: Theme) => createStyles({
    root: {
        flexGrow: 1,
    },
    paper: {
        padding: spacing.unit * 2,
        margin: spacing.unit * 2,
        textAlign: 'center',
        color: palette.text.secondary,
    },
    button: {
        margin: spacing.unit * 2,
    }
});

interface Props extends WithStyles<typeof styles> { };

export class ExampleForm extends Component<Props, State> {
  async handleSubmit(event: React.FormEvent<HTMLFormElement>) {
    // Handle form Submit
    ...
    if (errors) {
            window.alert('Some Error occurred');
            return;
        }
  }
  // render the form
}
export default withStyles(styles)(ExampleForm);
Run Code Online (Sandbox Code Playgroud)

测试文件

import FormWithStyles from './Form';

it('alerts on submit click', async () => {
  jest.spyOn(window,'alert').mockImplementation(()=>{});
  const spy = jest.spyOn(ActivityCreateStyles,'handleSubmit');
  const { getByText, getByTestId } = render(<FormWithStyles />)
  fireEvent.click(getByText('Submit'));

  expect(spy).toHaveBeenCalledTimes(1);
  expect(window.alert).toHaveBeenCalledTimes(1);
})
Run Code Online (Sandbox Code Playgroud)

jest.spyOn引发以下错误,Argument of type '"handleSubmit"' is not assignable to parameter of type 'never'.ts(2345)可能是因为ExampleForm封装在withStyles中。

我也尝试直接导入ExampleForm组件并手动分配样式,但无法这样做:

import {ExampleForm, styles} from './Form';

it('alerts on submit click', async () => {
  ...

  const { getByText, getByTestId } = render(<ActivityCreateForm classes={styles({palette,spacing})} />)

  ...
}
Run Code Online (Sandbox Code Playgroud)

出现以下错误: Type '{ palette: any; spacing: any; }' is missing the following properties from type 'Theme': shape, breakpoints, direction, mixins, and 4 more.ts(2345)

由于强大的类型和包装的组件,我发现很难用Typescript为Material-UI具有react-testing-library&的组件编写基本测试Jest。请指导。

Arn*_*las 6

首先,当您使用react-testing-library 的render方法时,您无需担心使用或任何包装器,因为最后它会呈现组件,因为它可能在真实的 dom 中,因此您可以正常编写测试。withStyles

然后,据我所知,您正在做与我开始测试时所做的相同的事情(这意味着您将变得擅长;)。您正在尝试模拟内部方法,这不是最好的方法,因为您需要做的是测试真正的方法。

所以让我们想象一下我们有一个Register用户组件。

源代码/注册.tsx

import ... more cool things
import * as api from './api';

const Register = () => {
  const [name, setName] = useState('');
  const handleNameChange = (event) => {
    setName(event.target.value);
  };

  const handleSubmit = (event) => {
    event.preventDefault();
    if (name) {
      api.registerUser({ name });
    }
  };

  return (
    <form onSubmit={handleSubmit}>
      <TextField
        id='name'
        name='name'
        label='Name'
        fullWidth
        value={name}
        onChange={handleNameChange}
      />
      <Button data-testid='button' fullWidth type='submit' variant='contained'>
        Save
      </Button>
    </form>
  );
}
Run Code Online (Sandbox Code Playgroud)

该组件非常简单,它是一个带有输入和按钮的表单。我们react hooks用于更改输入值,并基于在触发事件api.registerUser时调用与否handleSubmit

要测试组件,我们需要做的第一件事就是模拟 api.registerUser方法。

src/__tests__/Register.tsx

import * as api from '../api'

jest.mock('../api')

api.registerUser = jest.fn()
Run Code Online (Sandbox Code Playgroud)

这将允许我们查看该方法是否被调用。

接下来要做的是......编写测试,在这种情况下,我们可以测试两件事,看看是否handleSubmit正常工作。

  1. api.registerUser如果名称空,则不调用。
it('should not call api registerUser method', () => {
  const { getByTestId } = render(<Register />)
  fireEvent.click(getByTestId('button'))
  expect(api.registerUser).toHaveBeenCalledTimes(0)
})
Run Code Online (Sandbox Code Playgroud)
  1. api.registerUser如果名称不为空,调用。
it('should call api registerUser method', () => {
  const { getByLabelText, getByTestId } = render(<Register />)

  fireEvent.change(getByLabelText('Name'), { target: { value: 'Steve Jobs' }})

  fireEvent.click(getByTestId('button'))
  expect(api.registerUser).toHaveBeenCalledTimes(1)
})
Run Code Online (Sandbox Code Playgroud)

在最后一个测试中,我们也隐含地进行了测试,handleNameChange因为我们正在更改名称 :) 所以name不会为空,registerUser而是会被调用。

用这个例子withStyles打字稿在这个回购
演示在这里


小智 1

为什么不使用enzymeFull DOM Rendering呢?您可以使用simulate方法来模拟已安装组件上的事件。

class Foo extends React.Component {
  constructor(props) {
    super(props);
    this.state = { count: 0 };
  }

  render() {
    const { count } = this.state;
    return (
      <div>
        <div className={`clicks-${count}`}>
          {count} clicks
        </div>
        <a href="url" onClick={() => { this.setState({ count: count + 1 }); }}>
          Increment
        </a>
      </div>
    );
  }
}

const wrapper = mount(<Foo />);

expect(wrapper.find('.clicks-0').length).to.equal(1);
wrapper.find('a').simulate('click');
expect(wrapper.find('.clicks-1').length).to.equal(1);
Run Code Online (Sandbox Code Playgroud)