因此,我正在从基于类的组件转移到功能组件,但在使用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通过导出来编写测试。但是如何测试validateForm或handleSubmit方法呢?如果它是基于类的组件,那么我可以将组件变浅,并从实例中将其用作
validateEmail
validateForm
handleSubmit
const wrapper = shallow(<Login />); wrapper.instance().validateForm()
但这不适用于功能组件,因为无法以这种方式访问内部方法。有什么方法可以访问这些方法,还是应该在测试时将功能组件视为黑盒?
我认为,您不必担心单独测试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));
您可以重构:
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()) }
import { isPasswordValid, isEmailValid } from './Helpers'; .... const validateForm = () => { setIsLoginDisabled(!isPasswordValid(password) || !isEmailValid(email)); }; ....
这样,您就可以单独测试isPasswordValid和isEmailValid,然后测试时Login组件,你可以嘲笑你的进口。然后剩下的唯一要测试的Login组件就是单击,调用导入的方法,然后基于这些响应的行为,例如:
isPasswordValid
isEmailValid
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组件应该只处理 表单的更新, 而无需执行其他任何操作。可以直接测试。任何其他逻辑 都应分开处理 (关注点分离)。