enzymejs / enzyme

JavaScript Testing utilities for React
https://enzymejs.github.io/enzyme/
MIT License
19.96k stars 2.01k forks source link

The state is not reflect in the view when using useEffect and setTimeout #2519

Open mrdulin opened 3 years ago

mrdulin commented 3 years ago

Current behavior

SomeComponent.tsx:

import React, { ReactElement, useEffect, useState } from 'react';
import { StyledNotifyButton } from './styles';

export const SomeComponent = (): ReactElement => {
  const [showNotifyButton, toggleNotifyButton] = useState(false);

  useEffect(() => {
    setTimeout(() => {
      toggleNotifyButton(true);
    }, 5000);
  }, [toggleNotifyButton]);

  console.log('showNotifyButton: ', showNotifyButton);

  return (
    <div>
      <StyledNotifyButton visible={showNotifyButton} />
    </div>
  );
};

SomeComponent.test.tsx:

import { mount, ReactWrapper } from 'enzyme';
import React from 'react';
import { act } from 'react-dom/test-utils';
import { SomeComponent } from './SomeComponent';

describe('67440874', () => {
  let wrapper: ReactWrapper;

  beforeAll(() => {
    jest.useFakeTimers();
    wrapper = mount(<SomeComponent />);
  });
  it('should pass', () => {
    let notifyButton = wrapper.find('StyledNotifyButton');
    expect(notifyButton.prop('visible')).toBe(false);
    act(() => {
      jest.runOnlyPendingTimers();
    });
    // wrapper.update();
    expect(wrapper.find('StyledNotifyButton').prop('visible')).toBeTruthy();
  });
});

If I don't call wrapper.update() method, the changed state will not reflect in the view.

test result

 FAIL  examples/67440874/SomeComponent.test.tsx
  67440874
    ✕ should pass (11 ms)

  ● 67440874 › should pass

    expect(received).toBeTruthy()

    Received: false

      18 |     });
      19 |     // wrapper.update();
    > 20 |     expect(wrapper.find('StyledNotifyButton').prop('visible')).toBeTruthy();
         |                                                                ^
      21 |   });
      22 | });
      23 | 

      at Object.<anonymous> (examples/67440874/SomeComponent.test.tsx:20:64)

  console.log
    showNotifyButton:  false

      at SomeComponent (examples/67440874/SomeComponent.tsx:13:11)

  console.log
    showNotifyButton:  true

      at SomeComponent (examples/67440874/SomeComponent.tsx:13:11)

Test Suites: 1 failed, 1 total
Tests:       1 failed, 1 total
Snapshots:   0 total
Time:        1.542 s, estimated 2 s

Expected behavior

I don't know why I should call wrapper.update() here. As you can see, the state is changed, but the value of the visible prop is not changed.

API

Version

library version
enzyme ^3.11.0
react ^16.14.0
react-dom ^16.14.0
react-test-renderer
adapter (below)

Adapter

robertknight commented 3 years ago

I don't know why I should call wrapper.update() here.

Enzyme effectively takes a snapshot of your UI after the initial mount and when you call various Enzyme methods (eg. wrapper.setProps(...)). If you do something which causes the UI outside of Enzyme, such as the act block here, you need to tell Enzyme that the output as changed and it should take a new snapshot that you can then query. This is what wrapper.update() does.

From experience working with developers on my team this aspect of Enzyme can be pretty confusing if you've not developed a good mental model of how it works. It would definitely improve the user experience if the need for this could be eliminated.

ljharb commented 3 years ago

@robertknight fwiw v2 did this automatically; unfortunately (as i've come to believe in time) v3 changed this behavior to require an explicit update.

namgold commented 3 years ago

I have same issue here but in my case, I dont use any setTimeout magic to make Enzyme snapshot out of date. https://github.com/enzymejs/enzyme/issues/2542 Seem that Enzyme's .setProps() does not make Enzyme sync the snapshot as expected. It still requires an additional .update() to be call.