小编典典

使用Jest使用Hooks测试React功能组件

reactjs

因此,我正在从基于类的组件转移到功能组件,但在使用jest / zyme编写功能组件内部的方法(显式使用钩子)编写测试时陷入困境。这是我的代码的精简版。

function validateEmail(email: string): boolean {
  return email.includes('@');
}

const Login: React.FC<IProps> = (props) => {
  const [isLoginDisabled, setIsLoginDisabled] = React.useState<boolean>(true);
  const [email, setEmail] = React.useState<string>('');
  const [password, setPassword] = React.useState<string>('');

  React.useLayoutEffect(() => {
    validateForm();
  }, [email, password]);

  const validateForm = () => {
    setIsLoginDisabled(password.length < 8 || !validateEmail(email));
  };

  const handleEmailChange = (evt: React.FormEvent<HTMLFormElement>) => {
    const emailValue = (evt.target as HTMLInputElement).value.trim();
    setEmail(emailValue);
  };

  const handlePasswordChange = (evt: React.FormEvent<HTMLFormElement>) => {
    const passwordValue = (evt.target as HTMLInputElement).value.trim();
    setPassword(passwordValue);
  };

  const handleSubmit = () => {
    setIsLoginDisabled(true);
      // ajax().then(() => { setIsLoginDisabled(false); });
  };

  const renderSigninForm = () => (
    <>
      <form>
        <Email
          isValid={validateEmail(email)}
          onBlur={handleEmailChange}
        />
        <Password
          onChange={handlePasswordChange}
        />
        <Button onClick={handleSubmit} disabled={isLoginDisabled}>Login</Button>
      </form>
    </>
  );

  return (
  <>
    {renderSigninForm()}
  </>);
};

export default Login;

我知道我可以validateEmail通过导出来编写测试。但是如何测试validateFormhandleSubmit方法呢?如果它是基于类的组件,那么我可以将组件变浅,并从实例中将其用作

const wrapper = shallow(<Login />);
wrapper.instance().validateForm()

但这不适用于功能组件,因为无法以这种方式访问​​内部方法。有什么方法可以访问这些方法,还是应该在测试时将功能组件视为黑盒?


阅读 397

收藏
2020-07-22

共1个答案

小编典典

我认为,您不必担心单独测试FC内部的方法,而不必担心其副作用。例如:

  it('should disable submit button on submit click', () => {
    const wrapper = mount(<Login />);
    const submitButton = wrapper.find(Button);
    submitButton.simulate('click');

    expect(submitButton.prop('disabled')).toBeTruthy();
  });

由于您可能正在使用异步的useEffect,因此您可能希望将期望包装在 setTimeout中

setTimeout(() => {
  expect(submitButton.prop('disabled')).toBeTruthy();
});

您可能想做的另一件事是,提取与与表单介绍纯函数进行交互无关的任何逻辑。例如:代替:

setIsLoginDisabled(password.length < 8 || !validateEmail(email));

您可以重构:

Helpers.js

export const isPasswordValid = (password) => password.length > 8;
export const isEmailValid    = (email) => {
  const regEx = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;

  return regEx.test(email.trim().toLowerCase())
}

LoginComponent.jsx

import { isPasswordValid, isEmailValid } from './Helpers';
....
  const validateForm = () => {
    setIsLoginDisabled(!isPasswordValid(password) || !isEmailValid(email));
  };
....

这样,您就可以单独测试isPasswordValidisEmailValid,然后测试时Login组件,你可以嘲笑你的进口。然后剩下的唯一要测试的Login组件就是单击,调用导入的方法,然后基于这些响应的行为,例如:

- it('should invoke isPasswordValid on submit')
- it('should invoke isEmailValid on submit')
- it('should disable submit button if email is invalid') (isEmailValid mocked to false)
- it('should disable submit button if password is invalid') (isPasswordValid mocked to false)
- it('should enable submit button if email is invalid') (isEmailValid and isPasswordValid mocked to true)

这种方法的主要优点在于,Login组件应该只处理 表单的更新, 而无需执行其他任何操作。可以直接测试。任何其他逻辑 都应分开处理
关注点分离)。

2020-07-22