enzyme: .update() does not work when testing .setState within componentDidMount

Current behavior

I have the following componentDidMount method in my Overview class:

componentDidMount () {
  const tick = () => {
    return Overview.retrieveDevices()
      .then(devices => {
        this.setState({
          devices: devices,
          visibleDevices: devices.filter(this.searchDevices),
          isLoading: false
        })

        this.timer = setTimeout(tick, 1000)
      })
  }
  tick()
}

Overview.retrieveDevices() returns a promise with the data from another function that makes a fetch request to my server, the data it returns is an array.

I have the following set in my test suite:

import React from 'react'
import Overview from './Overview'
import Enzyme, { shallow, mount } from 'enzyme'
import Adapter from 'enzyme-adapter-react-16'

const nock = require('nock')

Enzyme.configure({ adapter: new Adapter() })

describe('Overview.jsx', () => {
  it('renders without crashing', () => {
    expect(shallow(<Overview />)).toMatchSnapshot()
  })

  it('calls the componentDidMount function when it is created', () => {
    const componentDidMountSpy = jest.spyOn(Overview.prototype, 'componentDidMount')
    mount(<Overview />)
    expect(componentDidMountSpy).toHaveBeenCalledTimes(1)
    componentDidMountSpy.mockRestore()
  })

  describe('componentDidMount', () => {
    let getNock

    beforeEach(() => {
      jest.useFakeTimers()
      getNock = nock('http://localhost:3001')
        .get('/api/v1/devices')
    })

    afterEach(() => {
      jest.useRealTimers()
      nock.cleanAll()
    })

    it('sets the devices state to an empty array when retrieve devices returns an empty array', () => {
      getNock.reply(200, [])
      const wrapper = mount(<Overview />)
      expect(wrapper.state('devices')).toEqual([])
    })

    it('sets the devices state to an array of devices when retrieve devices returns devices', () => {
      getNock.reply(200, [{id: '1234'}])
      const wrapper = mount(<Overview />)
      expect(wrapper.state('devices')).toEqual([])
      jest.runOnlyPendingTimers()
      wrapper.update()
      expect(wrapper.state('devices')).toEqual([{id: '1234'}])
    })
  })
})

It is the final test that is failing… I get the following error:

    expect(received).toEqual(expected)
    
    Expected value to equal:
      [{"id": "1234"}]
    Received:
      []
    
    Difference:
    
    - Expected
    + Received
    
    - Array [
    -   Object {
    -     "id": "1234",
    -   },
    - ]
    + Array []

      46 |       jest.runOnlyPendingTimers()
      47 |       wrapper.update()
    > 48 |       expect(wrapper.state('devices')).toEqual([{id: '1234'}])
      49 |     })
      50 |   })
      51 | })
      
      at Object.it (src/components/Overview/Overview.spec.jsx:48:40)

Expected behavior

The test to pass

Your environment

Linux, Ubuntu 16.04 yarn 1.3.2 node 8.9.0

API

  • shallow
  • mount
  • render

Version

library version
Enzyme 3.3.0
React 16.2.0

Adapter

  • enzyme-adapter-react-16
  • enzyme-adapter-react-15
  • enzyme-adapter-react-15.4
  • enzyme-adapter-react-14
  • enzyme-adapter-react-13
  • enzyme-adapter-react-helper
  • others ( )

About this issue

  • Original URL
  • State: closed
  • Created 6 years ago
  • Reactions: 8
  • Comments: 30 (10 by maintainers)

Most upvoted comments

This is still not working. Most of our state setting is done in componentDidMount. Can someone please help on this

Same issue here. componentDidMount is calling a promise that is calling setState, however, the test still can’t see the updated state.

I’ve inherited this from somewhere else, but we use it when we need to wait for unreachable promises.

// somewhere in your test setup code
global.flushPromises = () => {
	return new Promise(resolve => setImmediate(resolve))
}


test('something with unreachable promises', () => {
	expect.hasAssertions()
	const component = mount(<Something />)

        // do something to your component here that waits for a promise to return

	return flushPromises().then(() => {
		component.update() // still may be needed depending on your implementation
		expect(component.html()).toMatchSnapshot()
	})
})

I have also run into a similar issue, and the problem is not only in componentDidMount it is in general infact, that any programmatically triggered setState changes don’t take effect on UI, unless called externally in test cases.

same issue with enzyme@3.7.0, enzyme-adapter-react-16@1.6.0 and react@16.4

the only way the test will pass is if I call wrapper.setState({devices: [{id: '1234'}])

@ljharb Thanks for the call out, I went back and tweaked my test and verified it works without chaining. I’m not sure what happened the first time I wrote the tests and why it seemed to fix the async issues I was having. I also thought it was strange that it didn’t work. I appreciate you monitoring this thread.

I did have to use a setTimeout in conjunction with update() in some tests I wrote yesterday. On submitting the form, the component runs various validations and has two different rounds of setState(). (I know, not ideal)

         const form = mountWithTheme(<FormComponent {...props} />);
         form.find('button[type="submit"]').simulate('click');
         form.update();
         setTimeout(() => {
           expect(form.text()).toContain('Custom Field is required');
         }, 0);

I don’t know if this is the best solution, but I also had this issue and was able to get the test to see my updated state by putting my assertions inside a setTimeout. Maybe you could try something like this?

it('sets the devices state to an array of devices when retrieve devices returns devices', async (done) => {
  getNock.reply(200, [{id: '1234'}])
  const wrapper = await mount(<Overview />)
  
  setTimeout( () => {
    expect(wrapper.state('devices')).toEqual([])
    jest.runOnlyPendingTimers()
    wrapper.update()
    expect(wrapper.state('devices')).toEqual([{id: '1234'}])
    done()
  }, 500)
})