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

新手上路,请多包涵

因此,我正在从基于类的组件转移到功能组件,但是在使用 jest/enzyme 为显式使用钩子的功能组件内部的方法编写测试时卡住了。这是我的代码的精简版。

 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()

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

原文由 acesmndr 发布,翻译遵循 CC BY-SA 4.0 许可协议

阅读 768
2 个回答

在我看来,你不应该担心单独测试 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())
}

登录组件.jsx

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

这样你可以单独测试 isPasswordValidisEmailValid ,然后在测试 Login 组件时,你可以 模拟你的 imports 。然后剩下的唯一要测试你的 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 组件应该只处理 更新表单 而不是其他任何东西。这可以非常直接地进行测试。任何其他逻辑 都应单独处理关注点分离)。

原文由 Alex Stoicuta 发布,翻译遵循 CC BY-SA 4.0 许可协议

不能写评论,但你必须注意,Alex Stoicuta 说的是错误的:

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

这个断言总是会通过,因为……它从未被执行过。数一数你的测试中有多少断言并写下以下内容,因为只执行了一个断言而不是两个。所以现在检查你的测试是否有误报)

 it('should fail',()=>{
 expect.assertions(2);

 expect(true).toEqual(true);

 setTimeout(()=>{
  expect(true).toEqual(true)
 })
})

回答你的问题,你如何测试钩子?我不知道,自己寻找答案,因为出于某种原因 useLayoutEffect 没有为我测试……

原文由 John Archer 发布,翻译遵循 CC BY-SA 4.0 许可协议

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进