Search code examples
javascriptreactjsjestjsaxiosaxios-mock-adapter

Component making axios request being mounted twice in order for asynchronous test to pass, if it is mounted once the test fails. WHY


I am making a call to an internal server to give me back whatever data, and in order to mock this i am using axios-mock-adapter and sending back an array with 5 things. I have to mount component twice to make this test pass. here is my component:

   import React, { Component, Fragment } from 'react'
   import axios from 'axios'

  export default class HelloWorld extends Component {
     constructor(props) {
       super(props)
       this.state = {
        goodbye: false,
        data: []
    }
}

async componentDidMount() {
    await this.func()
    console.log("RUNNING");
}

func = async () => {
    let data;
    try {
        data = await axios.get('http://localhost:8080');
    }
    catch(e) {
        console.log("ERROR");
        throw(e)
    }

    this.setState({data: data.data})

}

goodbye = () => {
    this.setState((state, currentProps) => ({...state, goodbye: !state.goodbye}))
}

render() {
    return (
        <Fragment>
            <h1>
                Hello World
            </h1>
            <button id="test-button" onClick={this.goodbye}>Say Goodbye</button>
            {
                !this.state.goodbye ? null :
                <h1 className="goodbye">GOODBYE WORLD</h1>
            }
        </Fragment>
    )
}

}

and here is the test:

it('there is data being returned', async () => { 

    let mock = new MockAdapter(axios)
    const data = new Array(5).fill('Hello World')

    mock.onGet('http://localhost:8080').reply(200, data)

    const component =  await mount(<HelloWorld />) 


    //if this line below is commented out the test fails
    await component.instance().componentDidMount();

    expect(component.state('data')).toHaveLength(5)

})

not sure why i have to mount the component and then mount it again. Anyone have any ideas?


Solution

  • The mocked axios response happens asynchronously, so you won't get the response until the next tick of the event loop. This line:

    await component.instance().componentDidMount();
    

    is waiting for the response before continuing synchronous operation, which is why it works when it's there, and doesn't work when you remove that line. There are a few other solutions that will work - You can replace that line with this:

    await Promise.resolve();
    

    Or use a helper function like this:

    const wait = () => new Promise(resolve => setTimeout(resolve, 0));
    

    and replace your await component.instance().componentDidMount(); with this:

    await wait();
    

    Either of these will wait one tick of the event loop, which will allow the mocked response to come back. You might also need to call component.update() after you get the mock data. One other thing: mount(<HelloWorld />) is synchronous and doesn't return a promise, so no need to await it.