programing

Jest를 사용한 후크를 사용한 리액트 기능 컴포넌트 테스트

linuxpc 2023. 4. 1. 08:31
반응형

Jest를 사용한 후크를 사용한 리액트 기능 컴포넌트 테스트

클래스 베이스의 컴포넌트에서 기능 컴포넌트로 이행하고 있습니다만, 훅을 명시적으로 사용하는 기능 컴포넌트내의 메서드에 대해서, jaste/enzym을 사용해 테스트를 작성하는 동안, 막히고 있습니다.여기 내 코드의 제거 버전이 있습니다.

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 를 얕게 인스턴스에서 사용할 수 .

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

그러나 이 방법은 내부 메서드에 액세스할 수 없기 때문에 기능 컴포넌트에서는 작동하지 않습니다.이러한 방법에 액세스할 수 있는 방법이 있습니까? 또는 테스트 중에 기능 컴포넌트를 블랙박스로 취급해야 합니까?

FC의 부작용을 테스트하기보다는 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())
}

Login Component.jsx

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

을 볼 수 .isPasswordValid ★★★★★★★★★★★★★★★★★」isEmailValidLogin컴포넌트에서는 Import를 조롱할 수 있습니다.그리고 이제 남은 건 네 능력을 시험해 볼 수 있는 거야Login컴포넌트는 클릭 시 Import된 메서드가 호출되고 다음 응답에 기초한 동작이 됩니다.

- 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가 말한 내용은 틀렸습니다.

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

이 주장은 항상 통용될 것이다. 왜냐하면...실행이 안 된 적이 없어요두 개의 어설션 대신 하나의 어설션만 수행되므로 테스트에 몇 개의 어설션이 있는지 세고 다음 사항을 적습니다.지금 검사에서 잘못된 양성 반응이 있는지 확인하십시오.)

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

 expect(true).toEqual(true);

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

질문에 대한 답변입니다. 후크는 어떻게 테스트합니까?도 몰라, 있어. 어떤 에선가, 어떤 이유에선가, 어떤 이유에선지.useLayoutEffect날 위해 테스트되지 않고 있어

그래서 알렉스의 답변을 듣고 다음 방법으로 컴포넌트를 테스트할 수 있었습니다.

describe('<Login /> with no props', () => {
  const container = shallow(<Login />);
  it('should match the snapshot', () => {
    expect(container.html()).toMatchSnapshot();
  });

  it('should have an email field', () => {
    expect(container.find('Email').length).toEqual(1);
  });

  it('should have proper props for email field', () => {
    expect(container.find('Email').props()).toEqual({
      onBlur: expect.any(Function),
      isValid: false,
    });
  });

  it('should have a password field', () => {
    expect(container.find('Password').length).toEqual(1);
  });

  it('should have proper props for password field', () => {
    expect(container.find('Password').props()).toEqual({
      onChange: expect.any(Function),
      value: '',
    });
  });

  it('should have a submit button', () => {
    expect(container.find('Button').length).toEqual(1);
  });

  it('should have proper props for submit button', () => {
    expect(container.find('Button').props()).toEqual({
      disabled: true,
      onClick: expect.any(Function),
    });
  });
});

Alex가 말한 것처럼 상태 업데이트를 테스트하려면 다음 절차를 따릅니다.

it('should set the password value on change event with trim', () => {
    container.find('input[type="password"]').simulate('change', {
      target: {
        value: 'somenewpassword  ',
      },
    });
    expect(container.find('input[type="password"]').prop('value')).toEqual(
      'somenewpassword',
    );
  });

그러나 라이프 사이클 훅을 테스트하기 위해 얕은 렌더링에서는 아직 지원되지 않기 때문에 얕은 마운트 대신 마운트를 사용합니다.상태를 업데이트하지 않는 메서드는 별도의 유틸리티 파일 또는 React Function Component 외부에서 분리했습니다.또한 제어되지 않은 컴포넌트를 테스트하기 위해 데이터 속성 소품을 설정하여 값을 설정하고 이벤트를 시뮬레이션하여 값을 확인하였습니다.위의 예에 대한 리액트 기능 컴포넌트 테스트에 관한 블로그도 작성했습니다.https://medium.com/ @acesmndr / param-functional-components - with-param-using - f732124d320a

현재 효소는 React Hooks를 지원하지 않고 Alex의 답변은 맞지만, (저를 포함한) 사람들은 set Timeout()을 사용하여 Jest에 연결하는 데 어려움을 겪고 있는 것 같습니다.

다음으로 useEffect() 후크를 호출하고 useState() 후크를 호출하는 효소 얕은 래퍼를 사용하는 예를 나타냅니다.

// This is helper that I'm using to wrap test function calls
const withTimeout = (done, fn) => {
    const timeoutId = setTimeout(() => {
        fn();
        clearTimeout(timeoutId);
        done();
    });
};

describe('when things happened', () => {
    let home;
    const api = {};

    beforeEach(() => {
        // This will execute your useEffect() hook on your component
        // NOTE: You should use exactly React.useEffect() in your component,
        // but not useEffect() with React.useEffect import
        jest.spyOn(React, 'useEffect').mockImplementation(f => f());
        component = shallow(<Component/>);
    });

    // Note that here we wrap test function with withTimeout()
    test('should show a button', (done) => withTimeout(done, () => {
        expect(home.find('.button').length).toEqual(1);
    }));
});

또, 컴포넌트와 대화하는 beforeEach()에 대한 설명이 중첩되어 있는 경우, 각 콜이 timeout()에 들어가기 전에 랩해야 합니다.변경 없이 동일한 도우미를 사용할 수 있습니다.

isLoginDisabled 상태 대신 기능을 직접 사용하여 비활성화해 보십시오.예.

const renderSigninForm = () => (
<>
  <form>
    <Email
      isValid={validateEmail(email)}
      onBlur={handleEmailChange}
    />
    <Password
      onChange={handlePasswordChange}
    />
    <Button onClick={handleSubmit} disabled={(password.length < 8 || !validateEmail(email))}>Login</Button>
  </form>
</>);

테스트 케이스에서 버튼의 상태(활성화/비활성화)를 확인하려고 했을 때 상태 기대치를 얻지 못했습니다.그러나 disabled={isLoginDisabled}를 삭제하고 (password.length < 8 |!validateEmail(이메일))로 대체하면 매우 효과가 있었습니다.추신: 저는 리액션 초보자이기 때문에 리액션에 대한 지식은 매우 한정되어 있습니다.

언급URL : https://stackoverflow.com/questions/54713644/testing-react-functional-component-with-hooks-using-jest

반응형