samedi 24 février 2018

React/Jest - mock fetch and wait for componentDidMount to re-render

I'm playing around with react and jest and I've came to the following situation where I simply cannot figure it out how should I do it.

Todo.js

import React from 'react';
import PropTypes from 'prop-types';
import TodoItem from './TodoItem';
import {fetchTodoItems} from '../helpers/todo';

class Todo extends React.Component {
    constructor(props) {
        super(props);

        this.state = {
            todos: [],
            error: false,
            loading: true
        };

        this.updateMockState = this.updateMockState.bind(this);
    }

    updateMockState() {
        this.setState({
            todos: [{ id: 8101, text: "Cook fresh food", status: "completed" }],
            loading: false
        });
    }

    componentDidMount() {
        // TODO: add error handling

        fetchTodoItems().then(response => {
            this.setState({
                todos: response.data,
                loading: false
            })
        }, error => {});
    }

    render() {
        let content;

        // TODO: add error handling

        if (this.state.loading) {
            return (
                <div>
                    <div>
                        <button id="update-data" onClick={this.updateMockState}>Update State</button>
                    </div>
                    <p>Todos are being fetched...</p>
                </div>
            );
        }

        return (
            content ||
            <div>
                <div>
                    <button id="update-data" onClick={this.updateMockState}>Update State</button>
                </div>
                <p><b>{this.state.todos.length}</b> todos fetched so far</p>
                {this.state.todos.map(
                    (todo) => <TodoItem key={todo.id} id={todo.id} status={todo.status} text={todo.text} />)}
            </div>
        );
    }
}

Todo.proptypes = {
    timeout: PropTypes.number
};

export default Todo;

Todo.test.js

import React from 'react';
import ReactDOM from 'react-dom';
import renderer from 'react-test-renderer';
import { mount, shallow, render } from 'enzyme';
import Todo from '../components/Todo';
import Enzyme from 'enzyme';
import Adapter from 'enzyme-adapter-react-16';
import toJson from 'enzyme-to-json';

// TODO: remove sinon from NPM packages

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

const todos = { data: [{
    "id": 4404,
    "status": "active",
    "text": "Shopping List"
}, {
    "id": 7162,
    "status": "completed",
    "text": "Time Registration"
}]};

describe('Todo', () => {
    it('calls mock API', async () => {
        fetch = jest.fn().mockReturnValue(Promise.resolve(todos));
        const spy = jest.spyOn(Todo.prototype, 'componentDidMount');

        var component = mount(<Todo timeout={2000} />);
        component.instance().componentDidMount();
        expect(spy).toHaveBeenCalled();

        expect(toJson(component)).toMatchSnapshot();
    });
});

As you can see Todo component is a simple component that inside componentDidMount calls an API, get the response and displays. While waiting for the api call, some info is shown... There's also a button for dummy state update but that's not important for now.

fetchTodoItems (file is todo.js)

export const fetchTodoItems = () => {
    return fetch("data/todo.json").then(res => res.json());
};

My problem is the following:

  • I want to do a snapshot testing on Todo component as follows:
    1. First, right on render (before API call)
    2. Right after API call has finished with success

On 1st I'm supposed to see no todo items, but on 2nd I should see my todo items.

Here is TodoItem

import React from 'react';
import PropTypes from 'prop-types';

const TodoItem = (props) => {
    let htmlClass = [];
    if (props.status === 'completed') {
        htmlClass.push("todo-completed");
    }

    return (
        <ul>
            <p className={htmlClass.join(" ")}>
                <small>[#{props.id}]</small> {props.text} <i>({props.status})</i>
            </p>
        </ul>
    );
}

TodoItem.proptypes = {
    id: PropTypes.number.required,
    text: PropTypes.string.required,
    status: PropTypes.string.required
};

export default TodoItem;

So far I've tried the following:

  • pure snapshot testing with expect(component.toJSON()).toMatchSnapshot() - doesn't show results after API call

  • jest.spyON... the method is called but after that toMatchSnapshot still shows 1st snapshot with no data

  • return promise().then(... still no results

Any help?

EDIT:

If I remove API call from componentDidMount and use only setState, then snapshot testing shows todos items (without 1st case where it should say that todos are being fetched...) Why?? Shouldn't be the same?

If then only the promises are the root cause, how can I wait for all promises to finish?

Aucun commentaire:

Enregistrer un commentaire