Cannot read properties of undefined (reading 'find') in Jest and Enzyme React 17

Viewed 4230

I am working through a react app using v17. I have a component that adds an expense. The functionality in the component works as expected in the GUI but when I try to test it using jest/enzyme it throws an error of TypeError: Cannot read properties of undefined (reading 'find'). In the GUI it finds the expense I am trying to edit without issue. Am I not testing it correctly when trying to match a snapshot?

Edit Expense Component

import React from "react";
import { useParams } from "react-router-dom";
import { connect } from "react-redux";
import { ExpenseForm } from "./ExpenseForm";
import { editExpense, removeExpense } from "../actions/expenses";

//React router
import { useNavigate } from "react-router-dom";

export const EditExpensePage = (props) => {
  const navigate = useNavigate();
  const { expenseID } = useParams();

  const foundExpense = props.expenses.find(
    (expense) => expense.id === expenseID
  );

  return (
    <div>
      <h1>Edit Expense</h1>
      <ExpenseForm
        expense={foundExpense}
        onSubmit={(expense) => {
          // //Edit expense action expects 2 params (id, updates)
          props.editExpense(expenseID, expense);
          // //Redirect to dashboard
          navigate("/");
        }}
      />
      <button
        onClick={(e) => {
          e.preventDefault();
          props.removeExpense(expenseID);
          navigate("/");
        }}
      >
        Remove Expense
      </button>
    </div>
  );
};

const mapStateToProps = (state, props) => ({
  expenses: state.expenses
});

const mapDispatchToProps = (dispatch) => ({
  editExpense: (id, expense) => dispatch(editExpense(id, expense)),
  removeExpense: (id) => dispatch(removeExpense(id))
});

export default connect(mapStateToProps, mapDispatchToProps)(EditExpensePage);

Current Test

import React from "react";
import { shallow } from "enzyme";
import { EditExpensePage } from "../../components/EditExpensePage";
import { testExpenses } from "../fixtures/expenses";

let history, editExpense, removeExpense, wrapper;

//Mock for use navigate
const mockedUsedNavigate = jest.fn();
jest.mock("react-router-dom", () => ({
  ...jest.requireActual("react-router-dom"),
  useNavigate: () => mockedUsedNavigate
}));

const setup = (props) => {
  const component = shallow(
    <EditExpensePage
      {...props}
      expense={editExpense}
      history={history}
      removeExpense={removeExpense}
    />
  );
  return {
    component: component
  };
};

describe("EditForm component", () => {
  beforeEach(() => {
    setup();
  });

  test("should render EditExpensePage", () => {
    expect(wrapper).toMatchSnapshot();
  });
});

Updated editExpense value in test

const setup = (props) => {
  //editExpense = testExpenses[1]; //Same error
  editExpense = jest.fn(); //Same error
  let removeExpense = jest.fn();
  let history = jest.fn();
  const component = shallow(
    <EditExpensePage
      {...props}
      expense={editExpense}
      history={history}
      removeExpense={removeExpense}
    />
  );
  return {
    component: component
  };
};

Updated Test File

import React from "react";
import { shallow } from "enzyme";
import { EditExpensePage } from "../../components/EditExpensePage";
import { testExpenses } from "../fixtures/expenses";

let editExpense, expenseID, removeExpense, wrapper;

//Mock for use navigate
const mockedUsedNavigate = jest.fn();
jest.mock("react-router-dom", () => ({
  ...jest.requireActual("react-router-dom"),
  useNavigate: () => mockedUsedNavigate
}));

const setup = (props) => {
  expenseID = 1;
  editExpense = [testExpenses.find((expense) => expense.id === expenseID)];

  console.log(editExpense);
  //Output from this console log
    // [
    //    {
    //      id: 1,
    //      description: 'Wifi payment',
    //      note: 'Paid wifi',
    //      amount: 10400,
    //      createdAt: 13046400000
    //    }
    //  ]

  const component = shallow(
    <EditExpensePage
      {...props}
      expense={editExpense}
      removeExpense={removeExpense}
    />
  );
  return {
    component: component
  };
};

describe("EditForm component", () => {
  beforeEach(() => {
    setup();
  });

  test("should render EditExpensePage", () => {
    expect(wrapper).toMatchSnapshot();
  });
});
1 Answers

Updated your code

import React from "react";
import { shallow } from "enzyme";
import { EditExpensePage } from "../../components/EditExpensePage";
import { testExpenses } from "../fixtures/expenses";

let history, editExpense, removeExpense, wrapper;

//Mock for use navigate
const mockedUsedNavigate = jest.fn();
jest.mock("react-router-dom", () => ({
  ...jest.requireActual("react-router-dom"),
  useNavigate: () => mockedUsedNavigate
}));

const setup = (props) => {
  editExpense = testExpenses; //it should be an array like this [{ id: 1 }]
  const component = shallow(
    <EditExpensePage
      {...props}
      expenses={editExpense}
      history={history}
      removeExpense={removeExpense}
    />
  );
  return {
    component: component
  };
};

describe("EditForm component", () => {
  beforeEach(() => {
    setup();
  });

  test("should render EditExpensePage", () => {
    expect(wrapper).toMatchSnapshot();
  });
});

Your EditExpensePage is calling props.expenses, but in your test cases, you never set it up.

You only introduce it here

let history, editExpense, removeExpense, wrapper;

but you haven't set the value for editExpense which means it's undefined.

That's why undefined.find throws an error.

I'd suggest you set a mocked value for editExpense.

Related