0

I have a TextField (named EmailTextField) of which is used to enter e-mail address. When user enters email address and 'onBlur' event is occur, I have a async function named 'verifyUserNameAvailable' to make api call to server and check if email address is already taken or not.

Now, I want to create a unit test for this TextField. I do not want to test the 'verifyUserNameAvailable' as I already have a test for that in different test suite. Is there a way to mock

const response = await verifyUserNameAvailable(emailAddress);

call in handleOnBlur function?

ex. I want to unit test EmailTextField when response is 'true' and response is 'false'.

EmailTextField component

import React, {useRef, useState} from 'react';
import TextField from "@material-ui/core/TextField";
import Grid from "@material-ui/core/Grid";
import {verifyUserNameAvailable} from "../../../../api/auth/authApiConsumer";

export const EmailTextField = props => {

  const {onStateChange} = props;
  const [state, setState] = useState({
    errors: [],
    onChange: false,
    pristine: false,
    touched: false,
    inProgress: false,
    value: {
      email: '',
    },
  });
  const [currentReq, setCurrentReq] = useState(0);
  const latestReq = useRef(currentReq);

  const helperText = 'Email address will be used as your account id';
  const helperTextPristine = "'" + state.value.email + "' is available.";

  const handleOnBlur = async (event) => {

  const emailAddress = String(event.target.value).toLowerCase();



  // If true, verify username is available
  const updatedState = {
    ...state,
    touched: true,
    pristine: false,
    value: {
      email: emailAddress,
    },
    inProgress: true
  };
  setState(updatedState);
  onStateChange(updatedState);


  const response = await verifyUserNameAvailable(emailAddress);


  if (response === true) {

    const updatedState = {
      ...state,
      touched: true,
      pristine: true,
      value: {
        email: emailAddress,
      },
      inProgress: false,
      errors: [],
    };
    setState(updatedState);
    onStateChange(updatedState);

  } else {

    const updatedState = {
      ...state,
      touched: true,
      pristine: false,
      value: {
        email: emailAddress,
      },
      inProgress: false,
      errors: ["'" + emailAddress + "' is already used."],
    };
    setState(updatedState);
    onStateChange(updatedState);

  }

  };

  return (
      <Grid container spacing={1}>
        <Grid item xs={12}>
          <TextField
              variant="outlined"
              required
              fullWidth
              id="email"
              label="email address"
              error={state.errors.length > 0}
              helperText={state.errors.length > 0 ? state.errors[0]
                  : state.pristine === true ? helperTextPristine : helperText}
              name="email"
              autoComplete="email"
              margin='dense'
              onBlur={handleOnBlur}
          />
        </Grid>
      </Grid>
  )

};

export default EmailTextField;

Below is a unit test that I want to create.


import React from 'react';
import {configure} from 'enzyme';
import Adapter from 'enzyme-adapter-react-16';
import EmailTextField from './EmailTextField';
import TextField from '@material-ui/core/TextField';
import {createShallow} from '@material-ui/core/test-utils';
import {act} from 'react-dom/test-utils';

configure({adapter: new Adapter()});

describe('<EmailTextField />', () => {
  let shallow;

  beforeAll(() => {
    shallow = createShallow();
  });
  let wrapper;
  beforeEach(() => {
    wrapper = shallow(<EmailTextField onStateChange={handleStateChange}/>);
  });

  const handleStateChange = jest.fn()


  it('should show error when already registered account is entered', () => {

    // MOCK - Not a working code here.. but I want to do something like...
    when(verifyUserNameAvailable).isCalledReturn(false);
    ////////////////////////

    act(() => {
      wrapper.find(TextField).at(0).simulate('blur', {target: {value: '[email protected]'}});
    });
    wrapper.update();
    expect(wrapper.find(TextField).at(0).props().error).toBe(
        true);
    expect(wrapper.find(TextField).at(0).props().helperText).toBe(
        "[email protected] is already used.");
    expect(handleStateChange).toHaveBeenCalledWith({
      "errors": ["[email protected] is already used."],
      "inProgress": false,
      "onChange": false,
      "pristine": false,
      "touched": true,
      "value": {"email": "[email protected]"}
    });
  });

  it('should not show error when account is not previously registered', () => {

    // MOCK - Not a working code here.. but I want to do something like...
    when(verifyUserNameAvailable).isCalledReturn(true);
    ////////////////////////

    act(() => {
      wrapper.find(TextField).at(0).simulate('blur', {target: {value: '[email protected]'}});
    });
    wrapper.update();
    expect(wrapper.find(TextField).at(0).props().error).toBe(
        true);
    expect(wrapper.find(TextField).at(0).props().helperText).toBe(
        "[email protected] is available.");
    expect(handleStateChange).toHaveBeenCalledWith({
      "errors": ["[email protected] is available."],
      "inProgress": false,
      "onChange": false,
      "pristine": true,
      "touched": true,
      "value": {"email": "[email protected]"}
    });
  });

});

Is there a way to do something like this in above?

// MOCK - Not a working code here.. but I want to do something like...
when(verifyUserNameAvailable).isCalledReturn(true);
////////////////////////

update

I tried to create a mock under ../../../../api/auth/__mocks__/authApiConsumer

export const verifyUserNameAvailable = (email) => new Promise(
    function (resolve, reject) {
     resolve(true);
    });

and updated the unit test to look like


import React from 'react';
import {configure} from 'enzyme';
import Adapter from 'enzyme-adapter-react-16';
import EmailTextField from './EmailTextField';
import TextField from '@material-ui/core/TextField';
import {createShallow} from '@material-ui/core/test-utils';
import {act} from 'react-dom/test-utils';

jest.mock("../../../../api/auth/authApiConsumer");

configure({adapter: new Adapter()});

describe('<EmailTextField />', () => {
  let shallow;

  beforeAll(() => {
    shallow = createShallow();
  });
  let wrapper;
  beforeEach(() => {
    wrapper = shallow(<EmailTextField onStateChange={handleStateChange}/>);
  });

  const handleStateChange = jest.fn()


  it('should not show error when account is not previously registered', () => {

    act(() => {
      wrapper.find(TextField).at(0).simulate('blur', {target: {value: '[email protected]'}});
    });
    wrapper.update();
    expect(wrapper.find(TextField).at(0).props().error).toBe(
        true);
    expect(wrapper.find(TextField).at(0).props().helperText).toBe(
        "[email protected] is available.");
    expect(handleStateChange).toHaveBeenCalledWith({
      "errors": ["[email protected] is available."],
      "inProgress": false,
      "onChange": false,
      "pristine": true,
      "touched": true,
      "value": {"email": "[email protected]"}
    });
  });

});

But this does not work...

Looks like the mock function I created is being ignored or something..

1 Answer 1

1

You need to run the test in the next tick. Try this code. It works fine.

import React from 'react';
import { configure } from "enzyme";
import Adapter from "enzyme-adapter-react-16";
import EmailTextField from "./EmailTextField";
import TextField from "@material-ui/core/TextField";
import { createShallow } from "@material-ui/core/test-utils";
import { act } from "react-dom/test-utils";


jest.mock('./api/authApiConsumer');
const MOCK_MAIL = "[email protected]";

configure({ adapter: new Adapter() });

describe("<EmailTextField />", () => {
    const handleStateChange = jest.fn();
    let shallow;

    beforeAll(() => {
        shallow = createShallow();
    });
    let wrapper;
    it("should work", () => {
        wrapper = shallow(
            <EmailTextField onStateChange={handleStateChange} />
        );
    });

    it("should not show error when account is not previously registered", (done) => {
        act(() => {
            wrapper.find(TextField).at(0).simulate('blur', { target: { value: '[email protected]' } });
        });
        process.nextTick(() => {
            wrapper.update();
            expect(
                wrapper
                    .find(TextField)
                    .at(0)
                    .props().error
            ).toBe(false);
            expect(
                wrapper
                    .find(TextField)
                    .at(0)
                    .props().helperText
            ).toBe(`'${MOCK_MAIL}' is available.`);
            expect(handleStateChange).toHaveBeenCalledWith({
                errors: [],
                inProgress: false,
                onChange: false,
                pristine: true,
                touched: true,
                value: { email: MOCK_MAIL }
            });
            done();

        })
    });
});
Sign up to request clarification or add additional context in comments.

2 Comments

you need to change the jest mock path.
This works! Thanks very much. I am not sure how the nextTick work but I will need to play with it for a while. Thank you very much!

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.