3
votes

Here is my situation: I am trying to unit-test a React component (TodoList) that does nothing more on the Render method than map the items and show them. It gets the items (TodoItem) from the Redux store by using MapStateToProps.

This is the javascript code for the TodoList component:

class TodoList extends React.Component {
    onRemoveClick = (id) => {
        diContainer.dataLayer.todo.remove(id);
    }

    render() {
        const todos = this.props.todos;
        if(!todos)
            return null;

        return (
            todos.map(todo => (
                <TodoItem key={todo.id} todo={todo} onRemove={this.onRemoveClick} />
            ))    
        );
    }
}

const mapStateToProps = (state) => ({
    todos: state.todos
});

export default connect(mapStateToProps)(TodoList);

What I want to test now, is that whenever the button inside TodoItem (child object) gets called, the onRemoveClick delegate method gets called.

I tried using the function mocking that Jest provides, in conjunction with Enzyme. However, because TodoList gets his data from Redux, I have to surround my Enzyme mount() call with a Provider component and mock the store.

Here is my test code:

import React from 'react';
import { mount } from 'enzyme';

import { Provider } from 'react-redux';
import configureStore from 'redux-mock-store';

import TodoList from '../../../../react/components/todo/TodoList';

describe('TodoList component', () => {
    //global arrange
    const storeState = {
       todos: [
                {
                    id: 'testId1',
                    title: 'testTitle1',
                    description: 'testDescription1'
                },
                {
                    id: 'testId2',
                    title: 'testTitle2',
                    description: 'testDescription2'
                },
        ]
    };

    const mockStore = configureStore();
    let store;

    beforeEach(() => {
        store = mockStore(storeState)
    });

    it('Removes a todo from the list if the remove button the todo was pressed', () => {
        //arrange
        //let mockFn = jest.fn();
        //TodoList.prototype.onRemoveClick = mockFn; => tried, doesn't work...

        const component = mount(
            <Provider store={store}>
                <TodoList />
            </Provider>
        );

        //component.instance() => tried working with this, but couldn't find it
        //component.instance().children() => is not the TodoList

        const items = component.find('.todoItem');

        //act
        const button = items.first().find('button');
        button.simulate('click');

        //assert
        //Todo: check function spy here
    });
});

I commented out some things I tried. But I can't seem to be able to access the TodoList component in my test code, because of the Provider wrapper...

Any clues?

1

1 Answers

4
votes

Got it fixed through a lot of trial and error. I was able to access TodoList through the enzyme find functionality, which apparently also works on ComponentNames (and not just plain HTML selectors).

The second trick was to call forceUpdate() on the TodoList component AND update() on the parent wrapper.

it('Removes a todo from the list if the remove button on the todo was pressed', () => {
    //arrange
    const component = mount(
        <Provider store={store}>
            <TodoList />
        </Provider>
    );

    const list = component.find('TodoList').instance();
    let mockFn = jest.fn();
    list.onRemoveClick = mockFn;

    list.forceUpdate();
    component.update();

    //act
    const items = component.find('.todoItem');
    const button = items.first().find('button');
    button.simulate('click');

    //assert
    expect(mockFn).toHaveBeenCalled();
    expect(mockFn).toHaveBeenCalledTimes(1);
    expect(mockFn).toHaveBeenCalledWith('testId1');
});