简体   繁体   中英

onBlur with useState hook not working in Jest?

I'm new into testing code with react and jest; and jest is having a strange behavior towards hooks.

This is my component. It has 2 useState hooks, linked to 2 functions.

  • HandleChange will be triggered with onChange, and set the cardNumber with setCardNumber().
  • HandleBlur will be triggered with onBlur, and set the cardType with setCardType().

Just a simple component that works as it should.

import React, {useState} from 'react';

export const Test = () => {
  const [cardNumber, setCardNumber] = useState('');
  const [cardType, setCardType] = useState('');

  const handleOnChange = (event) => {
    setCardNumber(event.target.value);
  };

  const handleOnBlur = () => {
    setCardType(cardNumber);
  };

  return (
    <>
      <input type="text" value={cardNumber} onChange={handleOnChange} onBlur={handleOnBlur} />
      <p id="cn">{`CardNumber: ${cardNumber}`}</p>
      <p id="ct">{`CardType: ${cardType}`}</p>
    </>
  );
};

This is the test:

import React from 'react';
import {shallow} from 'enzyme';
import {Test} from '.';
import {act} from 'react-dom/test-utils';

describe('Sandbox Test', () => {
  it('should set cardNumber when doing onChange', async () => {
    const component = shallow(<Test />);

    const inputComponent = component.find('input');
    const value = {target: {value: '123456'}};

    await act(async () => {
      inputComponent.props().onChange(value);
      await component.update();
    });

    const cardNumberComponent = component.find('#cn');
    const cardTypeComponent = component.find('#ct');

    expect(cardNumberComponent.text()).toEqual(`CardNumber: 123456`);
    expect(cardTypeComponent.text()).toEqual(`CardType: `);
  });

  it('should set cardType when doing onBlur', async () => {
    const component = shallow(<Test />);

    const inputComponent = component.find('input');
    const value = {target: {value: '123456'}};

    await act(async () => {
      inputComponent.props().onChange(value);
      inputComponent.props().onBlur();
      await component.update();
    });

    const cardNumberComponent = component.find('#cn');
    const cardTypeComponent = component.find('#ct');

    expect(cardNumberComponent.text()).toEqual(`CardNumber: 123456`);
    expect(cardTypeComponent.text()).toEqual(`CardType: 123456`);
  });
});

I dunno what's happening when the onBlur action is triggered (I know it's been triggered), but somehow, it doesn't trigger setCardType(); and thus, this is the error I get when running the tests:

测试时控制台错误

What am I doing wrong? Been trying to debug this for the past couple of hours, and I'm sincerely clueless about what to do.

I think the call to .udpate() recreates the node tree with all the updated data but doesn't automatically update references to nodes you get with find() . So, the inputComponent you have the moment you simulate the onBlur event is the instance of the first render with an old version of the onBlur handler with an empty value in cardNumber state. You need to grab the input node again to get an updated instance before firing the onBlur event.

    ...
    let inputComponent = component.find("input"); 
    const value = { target: { value: "123456" } };

    await act(async () => {
      inputComponent.props().onChange(value);
      await component.update();
      inputComponent = component.find("input");
      inputComponent.props().onBlur();
      await component.update();
    });
    ...

https://codesandbox.io/s/input-onchange-onblur-enzyme-kdg0u?file=/src/MyComponent.test.tsx

I think this happens because you are calling .onChange() and .onBlur() in the same block of act() . So both onChange and onBlur run with the closure on the same cardType variable and only after that component re-renders with updated cardType .

Try with splitting:

  it('should set cardType when doing onBlur', async () => {
    const component = shallow(<Test />);

    const inputComponent = component.find('input');
    const value = {target: {value: '123456'}};

    await act(async () => {
      inputComponent.props().onChange(value);
      await component.update();
    });

    await act(async () => {
      inputComponent.props().onBlur();
      await component.update();
    });


    const cardNumberComponent = component.find('#cn');
    const cardTypeComponent = component.find('#ct');

    expect(cardNumberComponent.text()).toEqual(`CardNumber: 123456`);
    expect(cardTypeComponent.text()).toEqual(`CardType: 123456`);
  });

Also I'm not sure you really need await for act() .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM