1

I have a problem, which is how to write unit tests for the function below, function is login user with a sequence of actions such as user inputting data, pressing (fake action) button and sending request to server and response data to user Do I use Jest and Enzyme but I don't know how to do it, or am I misunderstanding unit tests in reactjs?

export default function Login({ title }) {
  UseStyles(s, n);

  Login.propTypes = {
    title: PropTypes.string.isRequired,
  };

  const [email, setEmail] = useState('');
  const [pw, setPw] = useState('');
  const [error,setError] = useState('');
  const [checkLogin, setCheckLogin] = useState('');

  // set validation
  const [validEmail, setValidEmail] = useState('');
  const [validPw, setValidPw] = useState('');

  useEffect(() => {

    if(Cookie.get('token')){
      setCheckLogin('hadLogin');
      window.history.back()
    }
  },[]);

  // notification login success
  function Notify() {
    toast("Loading...", {
      position: "top-right",
      autoClose: 3000,
    });
  }

  function disableBtn() {
    const exist = Object.values(validator.fields).includes(false);
    if(exist){
      return true;
    }
    return false;
  }

  // when click login button
  // Want to write unit test
  function handleSubmit() { // this is funtion i want to write unit test
    if (!pw || !email)
    {
      !pw ? setError('Password is empty') : ''
      !email ? setError('Email is empty') : ''
      return;
    }
    AxiosConfig
      .post('/users/login', {
        email,
        password: pw,
      },)
      .then(res => {
        Cookie.set('token', res.data.token,{expires:1});
        Cookie.set('email', email,{expires:1});
        Cookie.set('roleName', 'user',{expires:1});
        Notify();
        window.location.href = `${BaseUrl}`
      })
      .catch(err => {
        setError(err.response.data);
      });
  }

  return (
    checkLogin !== 'hadLogin' ?
    (<div className={s.root}>
      <div className={s.container}>
        <h1>{title}</h1>
        <p className={s.lead}>
          Log in with your email address.
        </p>
        <div className={s.formGroup}>
          <a className={s.facebook}>
            <svg
              className={s.icon}
              width="30"
              height="30"
              viewBox="0 0 30 30"
              xmlns="http://www.w3.org/2000/svg"
            >
              <path d="M22 16l1-5h-5V7c0-1.544.784-2 3-2h2V0h-4c-4.072 0-7 2.435-7 7v4H7v5h5v14h6V16h4z" />
            </svg>
            <span>Log in with Facebook</span>
          </a>
        </div>
        <div className={s.formGroup}>
          <a className={s.google}>
            <svg
              className={s.icon}
              width="30"
              height="30"
              viewBox="0 0 30 30"
              xmlns="http://www.w3.org/2000/svg"
            >
              <path
                d={
                  'M30 13h-4V9h-2v4h-4v2h4v4h2v-4h4m-15 2s-2-1.15-2-2c0 0-.5-1.828 1-3' +
                  '1.537-1.2 3-3.035 3-5 0-2.336-1.046-5-3-6h3l2.387-1H10C5.835 0 2 3.345 2 7c0' +
                  '3.735 2.85 6.56 7.086 6.56.295 0 .58-.006.86-.025-.273.526-.47 1.12-.47 1.735' +
                  '0 1.037.817 2.042 1.523 2.73H9c-5.16 0-9 2.593-9 6 0 3.355 4.87 6 10.03 6 5.882' +
                  '0 9.97-3 9.97-7 0-2.69-2.545-4.264-5-6zm-4-4c-2.395 0-5.587-2.857-6-6C4.587' +
                  '3.856 6.607.93 9 1c2.394.07 4.603 2.908 5.017 6.052C14.43 10.195 13 13 11' +
                  '13zm-1 15c-3.566 0-7-1.29-7-4 0-2.658 3.434-5.038 7-5 .832.01 2 0 2 0 1 0' +
                  '2.88.88 4 2 1 1 1 2.674 1 3 0 3-1.986 4-7 4z'
                }
              />
            </svg>
            <span>Log in with Google</span>
          </a>
        </div>
        <div className={s.formGroup}>
          <a className={s.twitter}>
            <svg
              className={s.icon}
              width="30"
              height="30"
              viewBox="0 0 30 30"
              xmlns="http://www.w3.org/2000/svg"
            >
              <path
                d={
                  'M30 6.708c-1.105.49-2.756 1.143-4 1.292 1.273-.762 2.54-2.56' +
                  '3-4-.97.577-2.087 1.355-3.227 1.773L25 5c-1.12-1.197-2.23-2-4-2-3.398 0-6' +
                  '2.602-6 6 0 .4.047.7.11.956L15 10C9 10 5.034 8.724 2 5c-.53.908-1 1.872-1' +
                  '3 0 2.136 1.348 3.894 3 5-1.01-.033-2.17-.542-3-1 0 2.98 4.186 6.432 7 7-1' +
                  '1-4.623.074-5 0 .784 2.447 3.31 3.95 6 4-2.105 1.648-4.647 2.51-7.53 2.51-.5' +
                  '0-.988-.03-1.47-.084C2.723 27.17 6.523 28 10 28c11.322 0 17-8.867 17-17' +
                  '0-.268.008-.736 0-1 1.2-.868 2.172-2.058 3-3.292z'
                }
              />
            </svg>
            <span>Log in with Twitter</span>
          </a>
        </div>
        <strong className={s.lineThrough}>OR</strong>
        <form method="post" >
          <div className={s.formGroup}>
            <label className={s.label} htmlFor="usernameOrEmail">
             Email address:
              <input
                className={s.input}
                id="usernameOrEmail"
                type="text"
                name="usernameOrEmail"
                maxLength={50}
                minLength={8}
                autoFocus // eslint-disable-line jsx-a11y/no-autofocus
                onChange={e => {setEmail(e.target.value)}}
                onBlur={e => setValidEmail(validator.fields.email)}
              />
              {/* Check validation */}
              {validator.message('email', email, 'required|email')}   {/* validator.message('field name', value to validate, 'validator rule') */}
              {/* Show validation error if having any */}
              <small className={s.validatorShow}>{validEmail === false ? validator.errorMessages.email : ''}</small>
            </label>
          </div>
          <div className={s.formGroup}>
            <label className={s.label} htmlFor="password">
              Password:
              <input
                className={s.input}
                id="password"
                type="password"
                name="password"
                onChange={e => setPw(e.target.value)}
                onBlur={e => {setValidPw(validator.fields.password); console.log(validator)}}
              />
               {/* Check validation */}
              {validator.message('password', pw, 'required|password')}
              {/* Show validation error if having any */}
              <small className={s.validatorShow}>{validPw === false ? validator.errorMessages.password : ''}</small>
            </label>
          </div>
          <div className={error ? s.alert : s.disable} >
              {error}
          </div>
          <div className={s.formGroup}>
          <ToastContainer/>
            <button className={disableBtn() ? s.buttonDisabled : s.button} type="button" onClick={handleSubmit} disabled={disableBtn()}>
              Log in
            </button>
          </div>
          <div className={s.link}>
            <a href="/forget">Forget Password</a>
          </div>
        </form>
      </div>
    </div>) : ''
  );
}
skyboyer
  • 22,209
  • 7
  • 57
  • 64
Hydraw
  • 41
  • 6

1 Answers1

1

You can approach this by using enzyme to mount your component and simulate the button click that executes the code you want to test.

Taken from https://airbnb.io/enzyme/docs/api/ReactWrapper/simulate.html and altered to suit your code:

const wrapper = mount(<Login title="test" />);

wrapper.find('.<INSERT WHATEVER s.button evaluates to>}').simulate('click');

Now you also need to mock out Cookie and AxiosConfig with jest. One way of doing this is using jest.spyOn(): https://jestjs.io/docs/en/jest-object#jestspyonobject-methodname and also some mock functions: https://jestjs.io/docs/en/mock-function-api

import AxiosConfig from 'whatever-npm-module"
import Cookie from 'whatever-npm-module"

const axiosSpy = jest.spyOn(AxiosConfig, 'post').mockResolvedValue({ data: { token: 'testToken' }});
const cookieSpy = jest.spyOn(Cookie, 'set');

You can now check what is sent to Cookie.set to help verify what you want to test with some jest expectations: https://jestjs.io/docs/en/expect

expect(cookieSpy).toHaveBeenNthCalledWith(1, 'testToken', {expires:1});
expect(cookieSpy).toHaveBeenNthCalledWith(2, whateverEmailIs, {expires:1});
...

This is a good starting point and you can go a bit further by using beforeEach and afterEach to set up the mocks, give mock return values and clear those return values in order to test your error scenarios.

JoelAK
  • 340
  • 2
  • 11
  • thank you @JoelAK for reply , i have a question :Can I make a connection between these events? For example, if I have to press, then the axios mock will be run, can I do this? If not, then how do I know if my unit test is running properly if the user actually uses it? – Hydraw Jan 13 '20 at 06:57
  • And const axiosSpy , where can I use it, please let me know :( – Hydraw Jan 13 '20 at 07:24
  • You absolutely can, essentially if you mock it at the start of your unit test file or at least before you mount the component then it will use the mocked version instead of the actual version of axios. AxiosSpy can be used like `expect(axiosSpy).toHaveBeenCalledWith('/users/login', { email, password, });` to ensure that whatever is set as email and password is used – JoelAK Jan 13 '20 at 22:45