7

I have a component MyComponent with method doSomething that updates a state, and renderChildren that renders child components based on these states:

class MyComponent extends Component {
  constructor(props) {
    super(props);
    ...
    this.state = {
      renderChildA: true,
      renderChildB: false,
    };
  }

  doSomething = (params) => {
    // Do something...

    this.setState({ 
      renderChildA: false,
      renderChildB: true,
    });
  }

  renderChildren = () => {
    const { renderChildA, renderChildB } = this.state;
    if (renderChildA) {
      return <ChildComponentA />;
    } else if (renderChildB) {
      return <ChildComponentB />;
    }
  }

  render() {
    return (
      <div>
        {this.renderChildren()}
        ...
      </div>
    );
  }
}

NOTE: the component is also connected to Redux, but has been simplified.

For unit testing, I'm calling doSomething and verifying that ChildComponentB is visible:

test('doSomething should trigger ChildComponentB to pop up', () => {
  const wrapper = shallow(
    <MyComponent
    ...
    />,
  ).dive();

  wrapper.instane().doSomething(params); // assume params is defined

  const childComponentB = wrapper.find('ChildComponentB');
  expect(childComponentB).toHaveLength(1);
});

However, the test case above fails:

my-component.test.jsx > Some test group name > doSomething should trigger ChildComponentB to pop up

    expect(received).toHaveLength(length)

    Expected value to have length:
      1
    Received:
      // Some giant JSON object
    recevied.length:
      0

      xxx |    const childComponentB = wrapper.find('ChildComponentB');
      xxx |    expect(childComponentB).toHaveLength(1);
      xxx |  });

Based on the logging, I'm seeing that setState within doSomething is triggered after expect statement in the test case. I suspect this is happening since setState is asynchronous, but I don't know a workaround for this (other than setTimeout).

Caladbolgll
  • 400
  • 1
  • 3
  • 15
  • in your test you are actually testing if ChildComponentB is there instead of ChildComponentA const **childComponentA** = container.find('**ChildComponentB**'); – acesaft Jul 31 '19 at 05:09
  • Yes, I'm checking if `ChildComponentB` is there. The variable name is a typo. – Caladbolgll Jul 31 '19 at 16:48

4 Answers4

0

You can try to manually update the component to get the latest state:

  wrapper.instance().doSomething(params); // assume params is defined
  wrapper.update(); 
  const childComponentB = container.find('ChildComponentB');
  expect(childComponentB).toHaveLength(1);

More info: https://airbnb.io/enzyme/docs/api/ShallowWrapper/update.html

Clarity
  • 10,730
  • 2
  • 25
  • 35
  • 1
    I've already tried this, but it results in the same behaviour described in the question. Even with `update()`, the state is still unchanged by the time `expect()` is called. – Caladbolgll Jul 31 '19 at 16:59
0

Not sure if this will work or not but lets try updating the doSomething function as.

doSomething = (params) => {
  this.setState({ 
    renderChildA: false,
    renderChildB: true,
  },() => this.renderChildren());
}
raman
  • 960
  • 8
  • 18
0

Found a rather simple solution. Use Async/Await:

test('doSomething should trigger ChildComponentB to pop up', async() => {
  const wrapper = shallow(
    <MyComponent
    ...
    />,
  ).dive();

  await wrapper.instane().doSomething(params);

  const childComponentB = wrapper.find('ChildComponentB');
  expect(childComponentB).toHaveLength(1);
});
Caladbolgll
  • 400
  • 1
  • 3
  • 15
-1
    it('check List component for column list', async () => {
    #ur code for re-render stuff
    await new Promise(resolve => setTimeout(resolve, 0)); // wait for response

    # this will sync ur wrapper with latest tree change
    wrapper = wrapper.update();  
});
Michael Rovinsky
  • 6,807
  • 7
  • 15
  • 30
qmkiwi
  • 135
  • 1
  • 5