setState value used in componentDidMount is not reflected in Enzyme test

Component.js

import React from 'react'
import request from 'superagent'

export default React.createClass({
    getInitialState() {
        return {cats: []}
    },

    componentDidMount() {
        request('/api', (err, res) => {
            if (err) return;
            this.setState({
                cats: res.body.results
            })
        })
    },

    render() {
        let cats = this.state.cats
        let catsList = (
            <ul>
                {cats.map((c) => <li key={c.id}>cat</li>)}
            </ul>
        )
        return (
            <div>
                {cats.length ? catsList : null}
            </div>
        )
    }
})

Component.test.js

jest.unmock('../app.js')
jest.unmock('superagent')

import React from 'react'
import {mount} from 'enzyme'
import nock from 'nock'
import App from '../app.js'

describe('App', () => {
    let ajaxFn
    beforeEach(() => {
        ajaxFn = nock('http://localhost')
            .get('/api')
            .reply(200, {
                results: [{id: 1}, {id: 2}, {id: 3}]
            })
    })

    it('renders correct number of cats', () => {
        let wrapper = mount(<App />)
        expect(wrapper.state('cats').length).toBe(3)
    })
})

The test does not pass. wrapper.state('cats').length is always 0.

I understand that setState doesn't guarantee to update state immediately, however if I log 'cats' in the component, I can see it updating.

Upvotes: 14

Views: 8065

Answers (3)

David
David

Reputation: 4857

I am not familiar with all the libraries you are using, but since your code is being executed asynchronously the test is finishing before the state can be updated. I was able to solve this problem by adding async/await to the test:

it('renders correct number of cats', async () => {
    let wrapper = await mount(<App />)
    expect(wrapper.state('cats').length).toBe(3)
})

Upvotes: 0

Simon Hardman
Simon Hardman

Reputation: 488

I had a similar problem and it was necessary to return a promise from the it callback and check the expectation in the then method of the promise.

In your case (assuming ajaxFn was a promise, or you could turn it into one) I think this would be approximately:

it('renders correct number of cats', () => {
    let wrapper = mount(<App />) 
    return ajaxFn.then(() => {
        expect(wrapper.state('cats').length).toBe(3);
    }
})

Upvotes: 2

Leland Richardson
Leland Richardson

Reputation: 2695

If you end up setting state in your component in some context that enzyme doesn't know about, you will have to manually call .update() on the wrapper in order for it to get the updated version of the render tree.

it('renders correct number of cats', () => {
    let wrapper = mount(<App />)
    expect(wrapper.update().state('cats').length).toBe(3)
})

Upvotes: 10

Related Questions