0
votes

Im using react hooks and using Use State for component states. While testing the component in jest i see i cant access state value and mock it either.

There are bunches of code which is looking for different values in state. since state is not acccesible i could t cover complete code coverage.

Please help me in writting a test case for below code.

const MyComponent = props => {
    const [counterSecond, setCounterSecond] = React.useState(8);
    const [counterFirst, setCounterFirst] = React.useState(0);

    const handleIncrement = () => {
        setCounterSecond(counterSecond + 1);
    };
    const handleDecrement = () => {
        setCounterSecond(counterSecond - 1);
    };

    React.useEffect(() => {
        if (counterSecond === 10) {
            setCounterSecond(0);
            setCounterFirst(1);
        }
        if (counterSecond === 3) {
            setCounterSecond(1);
            setCounterFirst(0);
        }
        if (counterSecond ===9) {
            setCounterSecond(2);
            setCounterFirst(1);
        }

    }, [counterSecond]);

    return (
        <div>
            <div onClick={handleIncrement} >Increment</div>
            <div onClick={handleDecrement} >Decrement</div>
        </div>
    );
};

export default MyComponent;

As you see the code is having useEffect, which looks after for the change in counterSecond value. But the internal conditions will only be covered when the state value matches 8 Or 3 Or 9.

Could you please guide me in writing Jest test case to cover the internal conditions in UserEffect.

1) And how to mock any state value
2) how to check of state value in Jest while using Hooks

1
I'm wondering if you component really need state at all if it does not use it in renderskyboyer
@skyboyer i want to know how it can be done.. so that i can apply to bigger application. Please suggestSumanth madey

1 Answers

2
votes

Let's assume your component renders counterFirst and counterSecond otherwise their existence just doesn't make any sense. Something like

....
    return (
        <div>
            <span test-id="counter">{`${counterFirst}:${counterSecond}`}</span>
            <div onClick={handleIncrement} id="inc">Increment</div>
            <div onClick={handleDecrement} id="dec">Decrement</div>
        </div>
    );

Then we would like to test our component. I strongly believe we don't need mock or assert against state or any internals. Instead we need to communicate through props and check render's result to check if component behaves as we expect.

So testing for initial state may look like:

function getCounter(wrapper) {
  return wrapper.find("['test-id'='counter']").text();
}

it('renders 0:8 by default', () => {
  const wrapper = mount(<MyComponent />);
  expect(getCounter(wrapper)).toEqual('0:8');
});

But what to do with that code in useEffect? If you have enzyme version recent enough it should just work. I believe. If it does not - check what version you use.

And returning to testing. Up to your code sample counter should react on incremention and decremention in slightly untypical way. Say for inc: 0:8 -> 1:2 -> 1:0 -> 1:1 -> 1:2 -> 1:0(because for 0:9 and 1:3 there is appropriate logic in useEffect that causes re-render). So we will test that:

function doInc(wrapper) {
  wrapper.find("inc").props().onClick();
} 
it('jumps from 0:8 to 1:2 on inc', () => {
  const wrapper = mount(<MyComponent />); // 0:8
  doInc(wrapper);
  expect(getCounter(wrapper)).toEqual('1:2');
});

Actually here logic should create values cycled(1:2 -> 1:0 -> 1:1 -> 1:2 -> 1:0) so I would test that in single test case. But maybe in your real component it does not loop.

You may wonder how could you set up initial state for some particular case? The same way: by calling props to imitate clicks and other communication.

function doDec(wrapper) {
  wrapper.find("dec").props().onClick();
} 
it('increments from 0:5 to 0:6', () => {
 const wrapper = mount(<MyComponent />); // 0:8
 doDec(wrapper); // 0:7
 doDec(wrapper); // 0:6
 doDec(wrapper); // 0:5
 doInc(wrapper); 
 expect(getCounter(wrapper)).toEqual('0:6');
});

Maybe it's good idea to make some helper function.

But would it be faster to set state directly? Yes, it would be. But that also would make tests non-reliable. Say, your sample code never achieves '0:9'. And that may be an error rather a goal. And we expect testing helps us realize that. By setting state directly we would never know there is an issue.