Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to unit test a React component that renders after fetch has finished?

I'm a Jest/React beginner. In jest's it I need to wait until all promises have executed before actually checking.

My code is similar to this:

export class MyComponent extends Component {
    constructor(props) {
        super(props);
        this.state = { /* Some state */ };
    }

    componentDidMount() {
        fetch(some_url)
            .then(response => response.json())
            .then(json => this.setState(some_state);
    }

    render() {
        // Do some rendering based on the state
    }
}

When the component is mounted, render() runs twice: once after the constructor runs, and once after fetch() (in componentDidMount()) finishes and the chained promises finish executing).

My testing code is similar to this:

describe('MyComponent', () => {

    fetchMock.get('*', some_response);

    it('renders something', () => {
        let wrapper = mount(<MyComponent />);
        expect(wrapper.find(...)).to.have.something();
    };
}

Whatever I return from it, it runs after the first time render() executes but before the second time. If, for example, I return fetchMock.flush().then(() => expect(...)), the returned promise executes before the second call to render() (I believe I can understand why).

How can I wait until the second time render() is called before running expect()?

like image 981
Antonis Christofides Avatar asked Aug 01 '17 17:08

Antonis Christofides


People also ask

How do you check if React component is rendered?

There's a checkbox well hidden in the React DevTools settings that allows you to visually highlight the components that rerendered. To enable it, go to "Profiler" >> click the "Cog wheel" on the right side of the top bar >> "General" tab >> Check the "Highlight updates when components render." checkbox.

Is componentDidUpdate called after every render?

The componentDidUpdate gets called after a render which means that you can access DOM nodes in it. This function receives props and states as parameters. It can also access new props and state with this.

How do you access a component props when it is rendered?

The child component uses the props argument to know what to render. Now, we can pass a function to the props object. You see we passed a function () => [ "nnamdi", "chidume" ] to ChildComponent via name ,then it can access it by referencing it as key in the props argument: this.props.name .

What happens when a component re renders?

A component will re-render itself if its parent re-renders. Or, if we look at this from the opposite direction: when a component re-renders, it also re-renders all its children. It always goes “down” the tree: the re-render of a child doesn't trigger the re-render of a parent.


2 Answers

I'd separate concerns, mainly because is easier to maintain and to test. Instead of declaring the fetch inside the component I'd do it somewhere else, for example in a redux action (if using redux).

Then test individually the fetch and the component, after all this is unit testing.

For async tests you can use the done parameter on the test. For example:

describe('Some tests', () => {
  fetchMock.get('*', some_response);

  it('should fetch data', (done) => { // <---- Param
    fetchSomething({ some: 'Params' })
      .then(result => {
        expect(result).toBe({ whatever: 'here' });
        done(); // <--- When you are done
      });
  });
})

The you can tests your component by just sending the loaded data in the props.

describe('MyComponent', () => {

  it('renders something', () => {
    const mockResponse = { some: 'data' };
    let wrapper = mount(<MyComponent data={mockResponse}/>);

    expect(wrapper.find(...)).to.have.something();
  });
});

When it comes to testing you need to keep it simple, if your component is difficult to test, then there's something wrong with your design ;)

like image 199
Crysfel Avatar answered Sep 22 '22 20:09

Crysfel


I've had some success with this, as it doesn't require wrapping or modifying components. It is however assuming there's only one fetch() in the component, but it can be easily modified if needed.

// testhelper.js

class testhelper
{
    static async waitUntil(fnWait) {
        return new Promise((resolve, reject) => {
            let count = 0;
            function check() {
                if (++count > 20) {
                    reject(new TypeError('Timeout waiting for fetch call to begin'));
                    return;
                }
                if (fnWait()) resolve();
                setTimeout(check, 10);
            }
            check();
        });
    }

    static async waitForFetch(fetchMock)
    {
        // Wait until at least one fetch() call has started.
        await this.waitUntil(() => fetchMock.called());

        // Wait until active fetch calls have completed.
        await fetchMock.flush();
    }
}

export default testhelper;

Then you can use it just before your assertions:

import testhelper from './testhelper.js';

it('example', async () => {
    const wrapper = mount(<MyComponent/>);

    // Wait until all fetch() calls have completed
    await testhelper.waitForFetch(fetchMock);

    expect(wrapper.html()).toMatchSnapshot();
});
like image 39
Malvineous Avatar answered Sep 22 '22 20:09

Malvineous