1

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?

skyboyer
  • 22,209
  • 7
  • 57
  • 64
mlisonek
  • 180
  • 12
  • this is as expected I think since your componentDidMount is async. In my project we need to add an utility to wait until we get expected state returned in a certain time. – duc mai Jan 31 '19 at 21:33

2 Answers2

1

Solved it by putting a beforeEach and passing done() to it like so:

 beforeEach( async (done) => {

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

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

    component =  await mount(<HelloWorld />)  

    done()
 })
mlisonek
  • 180
  • 12
0

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.

helloitsjoe
  • 6,264
  • 3
  • 19
  • 32
  • you are correct, however in my own solution, if I removed the await infront of mount the test will fail, so i'm not sure why that is if it is synchronous. I added await Promise.resolve() though, thanks for the tip! – mlisonek Feb 01 '19 at 15:38