Search code examples
reactjsunit-testingreact-reduxjestjsreact-testing-library

Not able to dispatch action in Jest


We have a cart component in the React app:

import { useDispatch, useSelector } from "react-redux";
import ItemList from "./ItemList";
import { clearCart } from "../slice/cartSlice";

const Cart = () => {
  const dispatch = useDispatch();
  const cartItems = useSelector((store) => store.cart.items);

  const handleClearCart = () => {
    console.log("Here in the handle clear cart onClick event handler");
    console.log("clearCart function is ", clearCart);
    dispatch(clearCart());
  };

  return (
    <div>
      <h1>Cart</h1>
      {cartItems.length != 0 && (
        <button onClick={handleClearCart}>
          Clear Cart
        </button>
      )}
      {cartItems.length ? (
        <div>
          <ItemList items={cartItems} />
        </div>
      ) : (
        <h3> Your cart is empty !</h3>
      )}
    </div>
  );
};

export default Cart;

This is the cartSlice we are using:

import { createSlice, current } from "@reduxjs/toolkit";

const cartSlice = createSlice({
  name: "cart",
  initialState: { items: [] },
  reducers: {
    addItem: (state, action) => {
      state.items.push(action.payload);
    },
    removeItem: (state, action) => {
      const filteredList = state.items.filter(
        (item, index) => index != action.payload
      );
      return {
        ...state,
        items: filteredList,
      };
    },
    clearCart: (state) => {
      console.log("Here in the clear cart reducer");
      state.items.length = 0;
    },
  },
});

export const { addItem, removeItem, clearCart } = cartSlice.actions;
export default cartSlice.reducer;

On the UI, when we click on the clear cart button, it clears the cart and displays the message "Your cart is empty".

We are trying to write a unit test for the same in Jest/ React Testing Library. Here is our attempt:

import { fireEvent, render, screen, waitFor } from "@testing-library/react";
import "@testing-library/jest-dom";
import { Provider } from "react-redux";
import configureMockStore from "redux-mock-store";
import mockStoreInfo from "../__mocks__/storeMock";
import Cart from "../src/components/Cart";

test("should clear cart when click on clear cart in cart component", async () => {
  render(
    <Provider store={storeMock}>
      <Cart />
    </Provider>
  );
  const clearCartButton = screen.getByRole("button", { name: "Clear Cart" });
  expect(clearCartButton).toBeInTheDocument();
  console.log("clearCart button is", clearCartButton);
  fireEvent.click(clearCartButton);
  expect(await (screen.getByText(" Your cart is empty !"))).toBeInTheDocument();
});

From what we can see, the clear Cart button is getting logged and is a valid button. The handleClearCart function is also getting called and we are able to print the clearCart function body as well. However, we can't see the console log from the clearCart action in the CartSlice, which indicates that it's not getting called, and consequently the test to assert Your cart is empty ! is failing. We are not sure why the action is not getting dispatched on click of that button. Can anyone help us out?

PS: The DOM Structure that we are getting from the failed test indicates that the cart has not been cleared. Here is the DOM:

<body>
  <div>
    <div class="cart-container" data-testid="cart">
      <h1 class="cart-heading">Cart</h1>
      <button class="clear-cart" data-testid="clear-cart">Clear Cart</button>
      <div class="cart-items" data-testid="cart-items-list">
        <div>
          <div class="menu-item">
            <div class="sc-guDLey byrpfT">
              <div class="menu-header">
                <span> Al Fungi Gourmet-Pizza </span>
                <span> - ₹ 549 </span>
              </div>
              <p class="accordion-body-description">
                Gourmet mushroom pizza on a cheesy Mozzarella base with
                jalapenos & cherry tomatoes.
              </p>
            </div>
            <div class="sc-beySPh fSyZNZ">
              <img
                class="accordion-image"
                src="https://res.cloudinary.com/swiggy/image/upload/fl_lossy,f_auto,q_auto,w_508,h_320,c_fill/4ffebb537f043e94e09e928f48e640e9"
              />
              <button class="accordion-button-delete">Remove -</button>
            </div>
          </div>
          <div class="menu-item">
            <div class="sc-guDLey byrpfT">
              <div class="menu-header">
                <span> Butterscotch Mousse Cake </span>
                <span> - ₹ 103.81 </span>
              </div>
              <p class="accordion-body-description">
                Sweet temptation! Butterscotch flavored mousse
              </p>
            </div>
            <div class="sc-beySPh fSyZNZ">
              <img
                class="accordion-image"
                src="https://res.cloudinary.com/swiggy/image/upload/fl_lossy,f_auto,q_auto,w_508,h_320,c_fill/e91002ccf7239fffeceaab4956a15a3a"
              />
              <button class="accordion-button-delete">Remove -</button>
            </div>
          </div>
          <div class="menu-item">
            <div class="sc-guDLey byrpfT">
              <div class="menu-header">
                <span> Choco Lava Cake </span>
                <span> - ₹ 109 </span>
              </div>
              <p class="accordion-body-description">
                Chocolate lovers delight! Indulgent, gooey molten lava inside
                chocolate cake
              </p>
            </div>
            <div class="sc-beySPh fSyZNZ">
              <img
                class="accordion-image"
                src="https://res.cloudinary.com/swiggy/image/upload/fl_lossy,f_auto,q_auto,w_508,h_320,c_fill/517791a68253a9a5084664b470f6e115"
              />
              <button class="accordion-button-delete">Remove -</button>
            </div>
          </div>
        </div>
      </div>
    </div>
  </div>
</body>

Solution

  • redux-mock-store is outdated; it doesn't actually manage any state or process dispatched actions. It is now the common recommendation and practice to create a real Redux store in unit testing.

    Example:

    import React from 'react';
    import { fireEvent, render, screen, waitFor } from "@testing-library/react";
    import "@testing-library/jest-dom";
    import { configureStore } from "@reduxjs/toolkit"; // <-- create real store
    import { Provider } from 'react-redux';
    import reducer from '../path/to/rootReducer'; // <-- import reducer
    import Cart from "../src/components/Cart";
    
    const preloadedState = {
      // any initial state necessary for the unit tests
    };
    
    const store = configureStore({
      preloadedState,
      reducer,
    });
    
    const Providers = ({ children }) => (
      <Provider store={store}>
        {children}
      </Provider>
    );
    
    test(
      "should clear cart when click on clear cart in cart component",
      async () => {
        render(<Cart />, { wrapper: Providers });
    
        const clearCartButton = screen.getByRole(
          "button",
          { name: "Clear Cart" }
        );
        expect(clearCartButton).toBeInTheDocument();
    
        console.log("clearCart button is", clearCartButton);
    
        fireEvent.click(clearCartButton);
    
        expect(
          await screen.getByText(" Your cart is empty !")
        ).toBeInTheDocument();
      }
    );
    

    See the Redux Writing Tests documentation for further general testing information and strategies.