Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Enzyme: on simulating "click", test asynchronously changed state value

I am trying to test a state value (say: state.name) which asynchronously changes on a button click.

Initially

  • state.nameis "Random Value"
  • When a button is clicked state.name changes to "peter" from "Random Value"
  • works as expected in browser
  • Need it to work the same on button.simulate('click') in my test

My component App.js:

import React, { Component } from 'react';

class App extends Component {
    state = {name: "Random Value"};
    render() {
        return (
            <div className="App">
                <div>{this.state.name}</div>
                <button onClick={this.handleClick}>GetData</button>
            </div>
        );
    }

    handleClick = () => {
        const currentContext = this;
        fetch('http://api-call/getdata')
            .then(function(response) {
                return response.json();
            })
            .then(function(jsonData) {
                // jsonData.name = "peter"
                currentContext.setState({name: jsonData.name});
            })
    }
}

export default App;

The test file App.test.js

describe('<App/>', () => {    
    it('should handle click correctly', () => {
        const wrapper = shallow(<App />);

        expect(wrapper.find('button').length).toBe(1);

        expect(wrapper.state().name).toEqual("Random Value");
        wrapper.find('button').simulate('click');
        expect(wrapper.update().state().name).toEqual("peter");
    });
});

Result:

// FAILED!

Expected value to equal:
      "peter"
Received:
      "Random Value"

What else have I tried?

All sorts of solutions out there like using async await, setImmediate and then wrapper.update() and many other things. Maybe I did something wrong or missed something. Anyways I have spent an evening trying to do it. I need help from enzyme experts.

Thanks

like image 746
Siddhartha Chowdhury Avatar asked Jan 01 '23 05:01

Siddhartha Chowdhury


1 Answers

First you need mock fetch somehow. Sending real request not only breaks isolation of unit-tests and adds risks of inconsistency. It's also harder to wait for response when you don't know when it may finish. There are different ways to achieve that. jest-fetch-mock is one of them.

Also I advice you don't check for state but rather check for render() results.

function getName(wrapper) {
    return wrapper.find('.App > div').at(0).props().children;
} 

it('should handle click correctly', async () => {
    fetch.mockResponseOnce(JSON.stringify({ name: '12345' }));
    const wrapper = shallow(<App />);

    expect(wrapper.find('button').length).toBe(1);

    expect(getName(wrapper)).toEqual("Random Value");
    wrapper.find('button').simulate('click');
    await Promise.resolve();
    expect(getName(wrapper)).toEqual("peter");
});

What's going on here. await Promise.resolve() is just just waits until all promises already resolved are run. It means without that our mocked response will not run between button click and expect runs.

Another way to get positive result is making handleClick() to return Promise we can await for:

...
handleClick = () => {
    const currentContext = this;
    return fetch('http://api-call/getdata')
        .then(function(response) {
            return response.json();
        })
        .then(function(jsonData) {
            // jsonData.name = "peter"
            currentContext.setState({name: jsonData.name});
        })
}
....
it('should handle click correctly', async () => {
     ....
    expect(getName(wrapper)).toEqual("Random Value");
    await wrapper.find('button').simulate('click');
    expect(getName(wrapper)).toEqual("peter");
});

or without async-await syntax:

it('should handle click correctly', () => {
     ....
    expect(getName(wrapper)).toEqual("Random Value");
    return wrapper.find('button').simulate('click').then(() => {
        expect(getName(wrapper)).toEqual("peter");
    });
});

But I really don't like this way since event handler have to return a Promise that it typically does not do.

More on microtasks(Promises)/macrotasks(timers, events) yuo may read here: https://abc.danch.me/microtasks-macrotasks-more-on-the-event-loop-881557d7af6f

More on testing async code in jest you better check their docs: https://jestjs.io/docs/en/asynchronous

like image 136
skyboyer Avatar answered Jan 03 '23 18:01

skyboyer