ckim16
ckim16

Reputation: 1749

how to test a react component after data is fetch in componentDidMount?

I have a react component that renders conditionally (renders if data is fetched otherwise returns null) and I want to test this with jest & enzyme. The problem that I'm having is I want to test one of the methods in the class but .instance() keeps returning null so it doesn't allow me to test the instance.

my code looks something like this

export default class MyComponent extends React.Component<Props, State> {
    componentDidMount() {
        this.props.fetchData.then(() => 
            this.setState({ loaded: true });
        );
    }

    methodThatIWantToTest() {
        //do some stuff here
    }

    render() {
        if (this.loaded) {
            // render stuff here
        } else {
            return null;
        }
    }
}

and in the test I want to test

describe('myComponent', () => {
   it('should do some stuff', () => {
      const shallowWrapper = shallow(<MyComponent {...props}/>);
      const method = shallowWrapper.instance().methodThatIWantToTest();
      ....such and such

   });
});

but it looks like MyComponent only returns null so shallowWrapper.instance() returns null as well. I tried shallowWrapper.update() and many other things but it seems it doesn't want to render at all.. How do I wait for my component to be updated and then starts expect statement?

has anyone had a similar issue as mine and know how to work around this?

Upvotes: 4

Views: 2645

Answers (2)

Estus Flask
Estus Flask

Reputation: 222493

It is render result and not an instance that is null. shallowWrapper.instance() is an instance of component class, it cannot be null for stateful component. As the reference states:

Returns (React 16.x)

ReactComponent: The stateful React component instance.

null: If stateless React component was wrapped.

While shallowWrapper.html() will be initially null indeed.

There is a mistake in original code, it should be this.state.loaded and not this.loaded:

MyComponent extends React.Component {
  state = { loaded: false };

  componentDidMount() {
    this.props.fetchData.then(() => {
          this.setState({ loaded: true });
    });
  }

  methodThatIWantToTest() {
      //do some stuff here
  }

  render() {
      if (this.state.loaded) {
          return <p>hi</p>;
      } else {
          return null;
      }
  }
}

componentDidMount and methodThatIWantToTest should be preferably considered different units. They belong to different tests. In case methodThatIWantToTest is called in lifecycle hooks, it may be stubbed in componentDidMount test:

   it('should fetch data', async () => {
      const props = { fetchData: Promise.resolve('data') };
      const shallowWrapper = shallow(<MyComponent {...props}/>);
      expect(shallowWrapper.html()).toBe(null);
      await props.fetchData;
      expect(shallowWrapper.html()).toBe('<p>hi</p>');
   });

Then the method can be tested separately. Lifecycle hooks can be disabled to reduce number of moving parts:

   it('should do some stuff', () => {
      const shallowWrapper = shallow(<MyComponent {...props}/>, {disableLifecycleMethods: true});
      const result = shallowWrapper.instance().methodThatIWantToTest();
      expect(result).toBe(...);    
   });

Upvotes: 1

Brian Adams
Brian Adams

Reputation: 45810

Here is a working example:


myComponent.js

import * as React from 'react';

export default class MyComponent extends React.Component {

  constructor(...props) {
    super(...props);
    this.state = { loaded: false };
  }

  componentDidMount() {
    this.props.fetchData().then(() =>
      this.setState({ loaded: true })
    );
  }

  methodThatIWantToTest() {
    return 'result';
  }

  render() {
    if (this.state.loaded) {
      return <div>loaded</div>;
    } else {
      return null;
    }
  }
}

myComponent.test.js

import * as React from 'react';
import { shallow } from 'enzyme';

import MyComponent from './myComponent';

describe('myComponent', () => {
  it('should do some stuff', async () => {
    const fetchData = jest.fn(() => Promise.resolve());
    const props = { fetchData };

    const shallowWrapper = shallow(<MyComponent {...props}/>);
    expect(shallowWrapper.html()).toBe(null);

    expect(shallowWrapper.instance().methodThatIWantToTest()).toBe('result');

    // pause the test and let the event loop cycle so the callback
    // queued by then() within componentDidMount can run
    await Promise.resolve();

    expect(shallowWrapper.html()).toBe('<div>loaded</div>');
  });
});

Upvotes: 0

Related Questions