Home > Software design >  React testing library: How to test if button was clicked?
React testing library: How to test if button was clicked?

Time:05-18

I'm pretty new to testing and started to work with react testing library.
The problem I've, is that fireEvent doesn't trigger, and the error throws, is following:

   Expected number of calls: >= 1
   Received number of calls:   0

So, I'd highly appreciate, if I could get answer for my questions:
For this particular test do I need to work with @testing-library/react-hooks?
Do I need to render my Product component wrapped on context Provider where the function is imported from?
I tried to reproduce the code and leave relevant code.
Product.tsx component

import React, { FunctionComponent } from "react";
import { useProducts } from "./ContextWrapper";
import { Button } from "@mui/material";
import { articleProps } from "./types";

const Product: FunctionComponent<{ article: articleProps }> = ({
  article,
}) => {
  const { handleAddToCart } = useProducts(); // this is from global context
  return (
      <Button
        aria-label="AddToCart"
        onClick={() => handleAddToCart(article)}
      >
        Add to cart
      </Button>

  );
};

export default Product;

Product.test.tsx

import React from "react";
import { fireEvent, render, screen } from "@testing-library/react";
import Product from "./Product";
import { act, renderHook } from "@testing-library/react-hooks";

const article = {
  name: "samsung",
  variantName: "phone",
  categories: [],
};

const renderProduct = () => {
  return render(
    <ContextProvider>
      <Product article={article} />
    </ContextProvider>
  );
};

//first test just tests if button exists with provided content and works fine
test("renders product", () => {
  render(<Product article={article} />);
  const AddToCartbutton = screen.getByRole("button", { name: /AddToCart/i });
  expect(AddToCartbutton).toHaveTextContent("Add to cart");
});
// this test throws the error described above
test("test addToCart button", () => {
  renderProduct();
  const onAddToCart = jest.fn();
  const AddToCartbutton = screen.getByRole("button", { name: /AddToCart/i });
  fireEvent.click(AddToCartbutton);
  expect(onAddToCart).toHaveBeenCalled();
});

ContextWrapper.tsx

import React, {createContext, useContext, ReactNode, useState} from "react";
import { prodProps } from "../types";

type ProductContextProps = {
  productData: prodProps;
  handleAddToCart: (clickedItem: productPropsWithAmount) => void;

};

const ProductContextDefaultValues: ProductContextProps = {
  productData: null as any,
  handleAddToCart:null as any;
};

type Props = {
  children: ReactNode;
};

const ProductContext = createContext<ProductContextProps>(
  ProductContextDefaultValues
);

export function useProducts() {
  return useContext(ProductContext);
}

const ContextWrapper = ({ children }: Props) => {
  const { data } = useGraphQlData();
  const [productData, setProductData] = useState<Category>(data);
  const [cartItems, setCartItems] = useState<prodProps[]>([]);

  useEffect(() => {
    if (data) {
      setProductData(data);
    }
  }, [data]);

  const handleAddToCart = (clickedItem: prodProps) => {
    setCartItems((prev: prodProps[]) => {
      return [...prev, { ...clickedItem }];
    });
  };

  return (
    <ProductContext.Provider
      value={{
        productData,
        handleAddToCart,
      }}
    >
      {children}
    </ProductContext.Provider>
  );
};

export default ContextWrapper;

Thanks in advance

CodePudding user response:

I advise you to create Button Component and test it separately like this:

const onClickButton = jest.fn();
await render(<Button onClick={onClickButton} />);
const AddToCartbutton = screen.getByRole("button", { name: /AddToCart/i });
await fireEvent.click(AddToCartbutton);
expect(onClickButton).toHaveBeenCalled();

CodePudding user response:

One way to accomplish this is to mock ContextWrapper, as your test is specifically referring to Product component.

So, you could do something like this into your test:

import * as ContextWrapper from '--- PATH TO ContextWrapper ---';


test('test addToCart button', () => {
  /// mockFunction
  const onAddToCart = jest.fn();

  jest.spyOn(ContextWrapper, 'useProducts').mockImplementationOnce(() => ({
    productData: {
      example_prodProp1: 'initialValue1',
      example_prodProp2: 'initialValue2',
    },
    // Set mockFunction to handleAddToCart of useProducts
    handleAddToCart: onAddToCart,
  }));

  render(<Product article={article} />);

  const AddToCartbutton = screen.getByRole('button', { name: /AddToCart/i });

  fireEvent.click(AddToCartbutton);

  expect(onAddToCart).toHaveBeenCalledWith({
    prodProp1: 'samsung',
    prodProp2: 'phone',
  });
});

In this line jest.spyOn(ContextWrapper, 'useProducts').mockImplementation we are mocking useProducts return value and setting the handleAddToCart function to your mockFunction and that's how you can check if it has been called.

* This test is strictly focused on Product component and you just want to garantee that the component calls the handleAddToCart function from ContextWrapper.

For test how handleAddToCart should work, you can create a specific test for the ContextWrapper.

  • Related