problem for mocking redux action creators functions in connected component

521 Views Asked by At

i am having problem to test and mock an action which passed through connect HOC redux in a connected component. i recieve this error:

console.error node_modules/@testing-library/react/dist/act-compat.js:52 TypeError: props.getSearchByQuery is not a function at onFinishHandler (E:\React Js Projects\kyc-admin-panel\src\screen\Dashboard\Users\components\UserSearch.jsx:31:13) at onFinish (E:\React Js Projects\kyc-admin-panel\node_modules\rc-field-form\lib\Form.js:89:9) at E:\React Js Projects\kyc-admin-panel\node_modules\rc-field-form\lib\useForm.js:762:11

here is my UserSearch component:

import React, { useEffect, useState } from "react";
import { connect, useSelector } from "react-redux";
import { getUserSearchQuery } from "../../../../store/Actions/UserAction";
import { Col, Form, Row, Button, Input, Select } from "antd";
import { userSearchFields } from "../../../../constants/SearchFields";

const { Option } = Select;

const UserSearch = (props) => {
  const roles = useSelector((state) => state.user.userRoles);
  const [roleList, setRoleList] = useState(roles);
  const [selectedRole, setSelectedRole] = useState("");
  const [form] = Form.useForm();

  useEffect(() => {
    setRoleList(roles);
  }, [roles]);

  const onFinishHandler = (values) => {
    const query = [];

    for (let key in values) {
      if (values[key] && values[key] !== undefined) {
        query.push(key + "=" + values[key]);
      }
    }

    if (query.length > 0) {
      const joinQueries = query.join("&");
      props.getUserSearchQuery(joinQueries);
      props.getSearchByQuery(joinQueries);
    } else {
      props.getUserSearchQuery("");
      props.getSearchByQuery("");
    }
  };
  const onResetForm = () => {
    form.resetFields();
    props.getUserSearchQuery("");
    props.getSearchByQuery("");
  };
  const fieldBox = [];
  for (let i = 0; i < userSearchFields.length; i++) {
    fieldBox.push(
      <Col
        lg={6}
        md={12}
        sm={24}
        xs={24}
        key={i}
        style={{ textAlign: "right", float: "right" }}
      >
        {userSearchFields[i].name == "role" ? (
          <Form.Item name={userSearchFields[i].name}>
            <Select
              onSelect={(value) => {
                setSelectedRole(value);
              }}
              className="role-dropdown"
              placeholder="نقش"
              role="listbox"
            >
              {roleList.length > 0
                ? roleList.map((item, index) => {
                    return (
                      <Option
                        data-testid="option"
                        key={index}
                        value={item.value}
                      >
                        {item.label}
                      </Option>
                    );
                  })
                : null}
            </Select>
          </Form.Item>
        ) : (
          <Form.Item name={userSearchFields[i].name}>
            <Input
              placeholder={userSearchFields[i].placeholder}
              name={userSearchFields[i].name}
              autoComplete="off"
              data-testid={userSearchFields[i].name}
            />
          </Form.Item>
        )}
      </Col>
    );
  }
  return (
    <Form
      form={form}
      className="user-advanced-search-form"
      onFinish={onFinishHandler}
    >
      <div className="user-searchbox-container">
        <Row className="user-search-fields-containers" gutter={24}>
          {fieldBox}
        </Row>
        <Row className="user-search-button-containers">
          <Button
            data-testid="search-button"
            type="primary"
            htmlType="submit"
            className="search-button"
          >
            جستجو
          </Button>

          <Button
            type="primary"
            htmlType="button"
            className="search-button filter-btn"
            onClick={onResetForm}
            data-testid="filter-button"
          >
            حذف فیلترها
          </Button>
        </Row>
      </div>
    </Form>
  );
};

export default connect(null, {
  getUserSearchQuery,
})(UserSearch);

my test file:

  import React from "react";
import UserSearch from "../../../../screen/Dashboard/Users/components/UserSearch";
import { render, fireEvent, cleanup } from "../../../../test-utils";
import "@testing-library/jest-dom/extend-expect";
import { wait } from "@testing-library/react";

  it("should call search function if one of the inputs filled and click on search button", async () => {
    const searchHandler = jest.fn();
    const { getByTestId, getByPlaceholderText } = render(
      <UserSearch getUserSearchQuery={searchHandler} />
    );
    const searchButton = getByTestId("search-button");
    const input = getByPlaceholderText("نام");
    fireEvent.change(input, { target: { value: "masoud" } });
    fireEvent.click(searchButton);
    await wait(() => {
      expect(searchHandler).toHaveBeenCalled();
    });
  });

test-utils.js

// test-utils.js
import React from "react";
import { render as rtlRender } from "@testing-library/react";
import { createStore, applyMiddleware } from "redux";
import { Provider } from "react-redux";
// Import your own reducer
import RootReducer from "./store/Reducers/index";
import thunk from "redux-thunk";

("use strict");
const customRender = (
  ui,
  {
    initialState,
    store = createStore(RootReducer, initialState, applyMiddleware(thunk)),
    ...renderOptions
  } = {}
) => {
  const Wrapper = ({ children }) => {
    return <Provider store={store}>{children}</Provider>;
  };
  return rtlRender(ui, { wrapper: Wrapper, ...renderOptions });
};

// override render method
export { customRender as render };
// re-export everything
export * from "@testing-library/react";

package.json:

{
  "name": "kyc-admin-panel",
  "version": "0.1.0",
  "private": true,
  "dependencies": {
    "@ant-design/icons": "^4.2.1",
    "@testing-library/jest-dom": "^4.2.4",
    "@testing-library/react": "^9.3.2",
    "@testing-library/user-event": "^7.1.2",
    "@types/jest": "^26.0.14",
    "antd": "^4.3.1",
    "axios": "^0.19.2",
    "classnames": "^2.2.6",
    "history": "^4.10.1",
    "jest-environment-node": "^26.3.0",
    "moment-jalaali": "^0.9.2",
    "node-fetch": "^2.6.1",
    "node-sass": "^4.14.1",
    "node-snackbar": "^0.1.16",
    "prop-types": "^15.7.2",
    "qs": "^6.9.4",
    "ramda": "^0.27.0",
    "react": "^16.13.1",
    "react-dom": "^16.13.1",
    "react-helmet": "^6.1.0",
    "react-redux": "^7.2.0",
    "react-router-dom": "^5.2.0",
    "react-scripts": "3.4.1",
    "reactstrap": "^8.5.1",
    "redux": "^4.0.5",
    "redux-thunk": "^2.3.0",
    "uuid": "^8.3.0"
  },
  "scripts": {
    "start": "react-scripts start",
    "build": "react-scripts build",
    "test": "react-scripts test",
    "eject": "react-scripts eject",
    "coverage": "react-scripts test --env=jsdom --watchAll=false --coverage"
  },
  "eslintConfig": {
    "extends": "react-app"
  },
  "browserslist": {
    "production": [
      ">0.2%",
      "not dead",
      "not op_mini all"
    ],
    "development": [
      "last 1 chrome version",
      "last 1 firefox version",
      "last 1 safari version"
    ]
  },
  "devDependencies": {
    "@babel/core": "^7.11.6",
    "@babel/plugin-proposal-optional-chaining": "^7.11.0",
    "@babel/plugin-syntax-dynamic-import": "^7.8.3",
    "@babel/preset-env": "^7.11.0",
    "@rollup/plugin-commonjs": "^11.0.2",
    "babel-core": "^7.0.0-bridge.0",
    "babel-jest": "^24.9.0",
    "babel-plugin-module-resolver": "^4.0.0",
    "babel-plugin-require-context-hook": "^1.0.0",
    "babel-plugin-styled-components": "^1.11.1",
    "babel-plugin-transform-es2015-modules-commonjs": "^6.26.2",
    "babel-plugin-transform-es5-property-mutators": "^6.24.1",
    "eslint": "^6.8.0",
    "eslint-plugin-react-hooks": "^2.3.0",
    "jest": "^24.9.0",
    "jest-css-modules": "^2.1.0",
    "prettier": "2.0.5",
    "redux-mock-store": "^1.5.4"
  }
}

2

There are 2 best solutions below

0
On

i fixed this by exporting the component in two different kinds:

const ConnectedUserSearch=connect(null, {
  getUserSearchQuery,
})(UserSearch)

export {UserSearch,ConnectedUserSearch} ;

the connectedUserSearch is used for running in real browser and UserSearch is used just for test purposes. you can mock redux actions easily if you don't use connected component.

2
On

If you wants to test your component then inside your UserSearch add this:

export UserSearch;

and use this export for testing, I face similar issues in my project so we decided to use HOC exported component for rendering and normal exported component for testing. I hope this will solve your problem else I don't know much about it.