Search code examples
reactjsreact-reduxjestjsreact-testing-libraryconnected-components

React Redux connected component with async action testing


I got a trouble with React-Redux connected component testing. I want to test this component with asynchronous actions, but in console I always getting an error... I use React-testing-library with jest for testing (not enzyme).

There is console error

expect(jest.fn()).toHaveBeenCalledWith(...expected)

Expected: [Function anonymous]
Received: [Function anonymous]

Number of calls: 1

  45 |   it('should dispatch an action on page first render', () => {
  46 |     expect(store.dispatch).toHaveBeenCalledTimes(1);
> 47 |     expect(store.dispatch).toHaveBeenCalledWith(fetchTop10());
     |                            ^
  48 |   });
  49 | });
  50 |<br /><br />

There is connected component "CardList.js"

import React, { Fragment, useEffect } from 'react';
import PropTypes from 'prop-types';
import { Link } from 'react-router-dom';
// Redux
import { connect } from 'react-redux';
// Actions
import { fetchTop10 } from '../../actions/fetchTitles';   // async api call with data response
// Utils
import CardItem from './CardItem';

const CardList = ({ fetchTop10, top10 }) => {
  useEffect(() => {
    (async () => {
      await fetchTop10();
    })();
  }, [fetchTop10]);

  const renderCards = () => {
    if (top10.length === 0) {   // This is for sceletion insted of spinner
      return [1, 2, 3, 4, 5, 6, 7, 8, 9, 10].map((el) => {
        return <CardItem key={el} />;
      });
    }

    return top10.map((title) => {   // Actual data from action API call
      return <CardItem key={title.id} title={title} />;
    });
  };

  return (
    <Fragment>
      <section id="section-top" className="section-top">
        <h3 className="section-top__heading">Week Bestlers</h3>
        <div className="top-cards">{renderCards()}</div>
        <Link to="/titles" className="section-top__btn btn-link">
          view more &rarr;
        </Link>
      </section>
    </Fragment>
  );
};

CardList.propTypes = {
  fetchTop10: PropTypes.func.isRequired,
  top10: PropTypes.array.isRequired,
};

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

export default connect(mapStateToProps, { fetchTop10 })(CardList);<br /><br />

Testing "CardList.test.js"

import { render, cleanup } from '@testing-library/react';
import '@testing-library/jest-dom';
import '@testing-library/jest-dom/extend-expect';
import React from 'react';
import { Provider } from 'react-redux';
import { Router } from 'react-router-dom';  // All project components are in global Router
import thunk from 'redux-thunk';
import configureStore from 'redux-mock-store';  // for provider store
// Component
import CardList from '../CardList';
// Objects
import { fakeTitle, fakeTitle2 } from '../../utils/UserTitleObjects'; // Just objects with data
import history from '../../../history';
import { fetchTop10 } from '../../../actions/fetchTitles';    // Action with API call

const mockStore = configureStore([thunk]);

describe('My Connected React-Redux Component', () => {
  let store, component;

  beforeEach(() => {
    store = mockStore({
      top10: [],
    });

    store.dispatch = jest.fn();

    component = render(
      <Provider store={store}>
        <Router history={history}>
          <CardList />
        </Router>
      </Provider>
    );
  });

  afterEach(cleanup);

  it('should render with given state from Redux store', () => {
    expect(component.baseElement.querySelectorAll('.sceletion').length).toBe(
      60
    );
  });

  it('should dispatch an action on page first render', () => {
    expect(store.dispatch).toHaveBeenCalledTimes(1);  // this is good  <--
    expect(store.dispatch).toHaveBeenCalledWith(fetchTop10());    // Here starts a problem
  });
});

Solution

  • You have to mock your action in this case, otherwise, your action would return another function which is pretty hard to assert.

    I would suggest to simply mock your action module like this:

    CardList.test.js

    
    // ...
    const mockStore = configureStore([thunk]);
    
    jest.mock("../../../actions/fetchTitles", () => ({
      __esModule: true,
      fetchTop10: jest.fn(),
    }));
    
    // ...