【问题标题】:How to mock stateless child component event when testing parent component测试父组件时如何模拟无状态子组件事件
【发布时间】:2020-11-30 05:30:18
【问题描述】:

如标题中所述,我正在尝试为<Search /> 组件设置一些测试,特别是我想测试useState 挂钩。 在模拟了 Redux 存储并创建了一个浅包装器之后,我尝试模拟来自子组件 DisplaySearchBar 的输入,但显然我什至无法选择它。 这就是我得到的错误:

Method “props” is meant to be run on 1 node. 0 found instead.

这里是 Search.js

import React, { useState } from 'react';
import { connect } from 'react-redux';
import PropTypes from 'prop-types';

import { handleScriptLoad } from '../../../helpers/Autocomplete';
import { getRestaurants, setAlert } from '../../../actions/restaurantAction';
import DisplaySearchBar from '../../layout/DisplaySearchBar/DisplaySearchBar';

import styles from './Search.module.scss';

const Search = ({ getRestaurants, setAlert }) => {
  const [where, setWhere] = useState('');
  const [what, setWhat] = useState('');
  const [sortBy, setSortBy] = useState('rating');

  const sortByOptions = {
    'Highest Rated': 'rating',
    'Best Match': 'best_match',
    'Most Reviewed': 'review_count',
  };

  // give active class to option selected
  const getSortByClass = (sortByOption) => {
    if (sortBy === sortByOption) {
      return styles.active;
    } else {
      return '';
    }
  };

  // set the state of a sorting option
  const handleSortByChange = (sortByOption) => {
    setSortBy(sortByOption);
  };

  //handle input changes
  const handleChange = (e) => {
    if (e.target.name === 'what') {
      setWhat(e.target.value);
    } else if (e.target.name === 'where') {
      setWhere(e.target.value);
    }
  };

  const onSubmit = (e) => {
    e.preventDefault();
    if (where && what) {
      getRestaurants({ where, what, sortBy });
      setWhere('');
      setWhat('');
      setSortBy('best_match');
    } else {
      setAlert('Please fill all the inputs');
    }
  };

  // displays sort options
  const renderSortByOptions = () => {
    return Object.keys(sortByOptions).map((sortByOption) => {
      let sortByOptionValue = sortByOptions[sortByOption];
      return (
        <li
          className={getSortByClass(sortByOptionValue)}
          key={sortByOptionValue}
          onClick={() => handleSortByChange(sortByOptionValue)}
        >
          {sortByOption}
        </li>
      );
    });
  };

  return (
    <DisplaySearchBar
      onSubmit={onSubmit}
      handleChange={handleChange}
      renderSortByOptions={renderSortByOptions}
      where={where}
      what={what}
      handleScriptLoad={handleScriptLoad}
    />
  );
};

Search.propTypes = {
  getRestaurants: PropTypes.func.isRequired,
  setAlert: PropTypes.func.isRequired,
};

export default connect(null, { getRestaurants, setAlert })(Search);

DisplaySearchBar.js

import React from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { clearSearch } from '../../../actions/restaurantAction';
//Import React Script Libraray to load Google object
import Script from 'react-load-script';
import Fade from 'react-reveal/Fade';
import Alert from '../Alert/Alert';

import styles from './DisplaySearchBar.module.scss';

const DisplaySearchBar = ({
  renderSortByOptions,
  onSubmit,
  where,
  handleChange,
  what,
  handleScriptLoad,
  restaurants,
  clearSearch,
}) => {
  const googleUrl = `https://maps.googleapis.com/maps/api/js?key=${process.env.REACT_APP_GOOGLE_API_KEY}&libraries=places`;
  // {googleUrl && <Script url={googleUrl} onLoad={handleScriptLoad} />}
  return (
    <section className={styles.searchBar}>
      <form onSubmit={onSubmit} className={styles.searchBarForm}>
        <legend className="title">
          <Fade left>
            <h1>Where are you going to eat tonight?</h1>
          </Fade>
        </legend>
        <Fade>
          <fieldset className={styles.searchBarInput}>
            <input
              type="text"
              name="where"
              placeholder="Where do you want to eat?"
              value={where}
              onChange={handleChange}
              id="autocomplete"
            />

            <input
              type="text"
              name="what"
              placeholder="What do you want to eat?"
              onChange={handleChange}
              value={what}
            />
            <div className={styles.alertHolder}>
              <Alert />
            </div>
          </fieldset>

          <fieldset className={styles.searchBarSubmit}>
            <input
              id="mainSubmit"
              className={`${styles.myButton} button`}
              type="submit"
              name="submit"
              value="Search"
            ></input>

            {restaurants.length > 0 && (
              <button
                className={`${styles.clearButton} button`}
                onClick={clearSearch}
              >
                Clear
              </button>
            )}
          </fieldset>
        </Fade>
      </form>
      <article className={styles.searchBarSortOptions}>
        <Fade>
          <ul>{renderSortByOptions()}</ul>
        </Fade>
      </article>
    </section>
  );
};

DisplaySearchBar.propTypes = {
  renderSortByOptions: PropTypes.func.isRequired,
  where: PropTypes.string.isRequired,
  handleChange: PropTypes.func.isRequired,
  what: PropTypes.string.isRequired,
  handleScriptLoad: PropTypes.func.isRequired,
  restaurants: PropTypes.array.isRequired,
  clearSearch: PropTypes.func.isRequired,
};

const mapStatetoProps = (state) => ({
  restaurants: state.restaurants.restaurants,
});

export default connect(mapStatetoProps, { clearSearch })(DisplaySearchBar);

和 Search.test.js

import React from 'react';
import { mount } from 'enzyme';
import configureStore from 'redux-mock-store';
import { Provider } from 'react-redux';

import Search from '../Search';
import DisplaySearchBar from '../../../layout/DisplaySearchBar/DisplaySearchBar';

const mockStore = configureStore();
const initialState = {
  restaurants: { restaurants: ['foo'], alert: null },
};

describe('Search', () => {
  test('renders withut errors', () => {
    const store = mockStore(initialState);
    const wrapper = mount(
      <Provider store={store}>
        <Search setAlert={jest.fn()} getRestaurants={jest.fn()} />
      </Provider>
    );
    wrapper.find(DisplaySearchBar).props();
  });
});

感谢您的帮助!

【问题讨论】:

  • 我会尝试像 'wrapper.find('DisplaySearchBar')' 那样找到或使用 'mount' 而不是浅的。你试过了吗?
  • 您也可以添加快照并查看它实际呈现的效果。

标签: reactjs testing mocking jestjs enzyme


【解决方案1】:

shallow 不适用于 react-redux 新版本 (&gt;= 6)。

使用 mount 代替:

    const wrapper = mount( // <-- changed shallow to mount.
      <Provider store={store}>
        <Search {...props} />
      </Provider>
    );

Run It On Sandbox(使用tests 选项卡运行测试。)

【讨论】:

  • 我用mount 替换了shallow,现在我得到错误```警告:道具类型失败:道具restaurantsDisplaySearchBar 中被标记为必需,但它的值是undefined.``` 基本上子组件DisplaySearchBar 无法从商店访问状态或功能。我尝试将相同的初始状态从真实商店添加到模拟商店,但它没有改变任何东西。我已经用新代码更新了问题
  • 您的解决方案是正确的,我在模拟商店中提供的初始状态不正确。现在它就像一个魅力。我已经用正确的实现更新了Search.test.js
【解决方案2】:

尝试像这样安装它:

const wrapper = shallow(
    <Provider store={store} />
        <Search setAlert=jest.fn() getRestaurants=jest.fn() />
    </Provider>
);

【讨论】:

    猜你喜欢
    • 2019-05-20
    • 1970-01-01
    • 1970-01-01
    • 2020-03-05
    • 2019-06-07
    • 1970-01-01
    • 1970-01-01
    • 2017-07-10
    • 2023-03-31
    相关资源
    最近更新 更多