如何在反应测试中检查状态是否已更改?

时间:2020-02-21 17:20:53

标签: reactjs testing

我对在React中进行测试非常陌生。我在排序功能的末尾更新状态。我只能弄清楚如何从我正在创建的包装器中获取状态。

test('Sorting Data No Change', () => {
   const wrapper = shallow(render(<Table headers={[
                       {name:"ID", prop: "id"},
                       {name:"Manufacturer", prop: "manufacturer"},
                       {name:"Model", prop: "model"}
                       ]}
              data={data}
              bold ="Ford"
              upper="Model" />);


        );


  console.log(wrapper.instance().sort("asend", "id"));
  expect(wrapper.state.filtered).equals(data);
});

2 个答案:

答案 0 :(得分:0)

您可以获取更新前后的状态,并像这样检查它们:

test('Sorting Data No Change', () => {
   const wrapper = shallow(render(<Table headers={[
                       {name:"ID", prop: "id"},
                       {name:"Manufacturer", prop: "manufacturer"},
                       {name:"Model", prop: "model"}
                       ]}
              data={data}
              bold ="Ford"
              upper="Model" />);


        );

   let filterState = wrapper.state.filtered;

  // update the state

  wrapper.instance().sort("asend", "id");

// check the new state equals to the past state

  expect(wrapper.state.filtered).equals(filterState);
});

答案 1 :(得分:0)

有几种测试组件的方法。您可以装载父级,模拟点击并针对父级state和子级props进行断言。或者,您可以挂载子级,操纵props以模拟排序单击,并针对prop数据中的更改进行断言。 要做的最终要取决于您和您的工作流程

也就是说,我喜欢在一个.test.js文件中测试两个组件,当两个组件固有地链接为一个组件时(意味着,一个组件不能没有另一个组件就无法工作)。因此,在下面的示例中,当state发生更改时,我将针对父props进行声明,并对某些子元素进行一些声明。

工作示例(点击Tests标签以运行测试)

Edit Testing Parent State

测试中发生了什么(您也可以在Browser标签中进行操作以了解流程):

  1. 模拟在id表标题旁边的“排序”图标上的初始点击
  2. 对父状态进行断言:datasortByNamesortByType;然后对孩子的可见元素进行断言(在这种情况下,请确保id旁边的“排序”图标根据点击顺序更改为向上/向下箭头-第一次单击为{{ 1}},第二次点击是desc,然后在随后的点击中在两者之间切换。
  3. 单击asc按钮以重置为Clear Filters

components / App / index.js

initialState

components / App / __ tests __ / App.test.js

import React, { Component } from "react";
import Table from "../Table";
import { app } from "./App.module.scss";

export const initialState = {
  data: [
    {
      id: "1",
      manufacturer: "Ford",
      model: "Mustang"
    },
    {
      id: "2",
      manufacturer: "Toyota",
      model: "Tundra"
    },
    {
      id: "3",
      manufacturer: "Honda",
      model: "Civic"
    }
  ],
  headers: ["id", "manufacturer", "model"],
  sortByName: "",
  sortByType: ""
};

class App extends Component {
  state = initialState;

  clearFilters = () => this.setState(initialState);

  handleSort = (sortByName, sortByType) => {
    this.setState(prevState => ({
      data: Array.from(prevState.data).sort((a, b) =>
        sortByType === "asc"
          ? a[sortByName].localeCompare(b[sortByName])
          : b[sortByName].localeCompare(a[sortByName])
      ),
      sortByName,
      sortByType
    }));
  };

  render = () => (
    <div className={app}>
      <Table
        {...this.state}
        clearFilters={this.clearFilters}
        handleSort={this.handleSort}
      />
    </div>
  );
}

export default App;

components / Table / index.js

import React from "react";
import { configure, mount } from "enzyme";
import Adapter from "enzyme-adapter-react-16";
import App, { initialState } from "../index";

configure({ adapter: new Adapter() });

describe("App", () => {
  let wrapper;
  beforeEach(() => {
    // before each test, reset the wrapper to initial state
    wrapper = mount(<App />);
  });

  it("should render without errors", () => {
    expect(wrapper.find("Table").exists()).toBeTruthy();
    expect(wrapper.find("FaSort").length).toEqual(3);
  });

  it("sorts data by a table header sort button", () => {
    // a function to simulate a sort click on the icon next to the "id" table header
    const sortById = () => wrapper.find("button#sort-id").simulate("click");

    // simulating an initial sort click
    sortById();

    // next to id should should now be a "sort down" arrow icon
    expect(wrapper.find("FaSortDown").length).toEqual(1);

    // the "sortByName" state should now be "id"
    expect(wrapper.state("sortByName")).toEqual("id");
    // the "sortByType" state should now be "desc"
    expect(wrapper.state("sortByType")).toEqual("desc");

    // the "data" state should be now sorted by desc id
    expect(wrapper.state("data")).toEqual([
      {
        id: "3",
        manufacturer: "Honda",
        model: "Civic"
      },
      {
        id: "2",
        manufacturer: "Toyota",
        model: "Tundra"
      },
      {
        id: "1",
        manufacturer: "Ford",
        model: "Mustang"
      }
    ]);

    // simulating the sort click again
    sortById(); 

    // next to the "id" should be now be a "sort up" arrow icon
    expect(wrapper.find("FaSortUp").length).toEqual(1);

    // the "sortByType" state should now be "asc"
    expect(wrapper.state("sortByType")).toEqual("asc");

    // the "data" state should be sorted by the asc id
    expect(wrapper.state("data")).toEqual([
      {
        id: "1",
        manufacturer: "Ford",
        model: "Mustang"
      },
      {
        id: "2",
        manufacturer: "Toyota",
        model: "Tundra"
      },
      {
        id: "3",
        manufacturer: "Honda",
        model: "Civic"
      }
    ]);

    // clicking 'clear filters' button to reset to initial state
    wrapper.find("button#clear-filters").simulate("click");

    expect(wrapper.state()).toEqual(initialState);
  });
});

注释

我正在使用一些ES6速记语法,因此,如果看起来有些混乱,那么这里有一些注意事项:

  • Ternary operatorimport React from "react"; import PropTypes from "prop-types"; import { FaSort, FaSortUp, FaSortDown } from "react-icons/fa"; // import { table, thead, th, td, headerColum, sort, clear } from "./Table.module.scss"; const Table = ({ clearFilters, data, handleSort, headers, sortByName, sortByType }) => ( <> <table className={table}> <thead className={thead}> <tr> {headers.map(name => ( <th className={th} key={name}> <span className={headerColum}>{name}</span> <button id={`sort-${name}`} className={sort} type="button" onClick={() => handleSort( name, !sortByType || sortByName !== name || sortByType === "asc" ? "desc" : "asc" ) } key={name} > {sortByName !== name ? ( <FaSort /> ) : sortByType === "asc" ? ( <FaSortUp style={{ color: "#0093fc" }} /> ) : ( <FaSortDown style={{ color: "#0093fc" }} /> )} </button> </th> ))} </tr> </thead> <tbody> {data.map(({ id, manufacturer, model }) => ( <tr key={id}> <td className={td}>{id}</td> <td className={td}>{manufacturer}</td> <td className={td}>{model}</td> </tr> ))} </tbody> </table> <button type="button" id="clear-filters" className={clear} onClick={clearFilters} > Clear Filters </button> </> ); Table.propTypes = { clearFilters: PropTypes.func.isRequired, data: PropTypes.arrayOf( PropTypes.shape({ id: PropTypes.string.isRequired, manufacturer: PropTypes.string.isRequired, model: PropTypes.string.isRequired }).isRequired ).isRequired, handleSort: PropTypes.func.isRequired, headers: PropTypes.arrayOf(PropTypes.string).isRequired, sortByName: PropTypes.string, sortByType: PropTypes.string }; export default Table; )是cond ? true : false的简写。
  • Spread syntax允许您“扩展”对象的属性。由于if/else是具有属性的对象,我们可以执行以下操作:state,这是以下简称的缩写:<Table {...this.state} />
  • 由于<Table data={this.state.data} sortByName={this.state.sortByName} />是异步的,因此我使用callback function来确保状态为最新的setState。当处理大数据和/或多个this.setState(prevState => ({ ... })调用时,这可以确保在评估状态时状态是准确的(同步的)。另外,我使用括号this.setState()作为对象()隐式地return函数的结果,例如:{}将返回const example = () => ({ name: "hello" });具有object属性:name
  • 由于const test = example(): // returns { name: "hello" }更改了数组,因此Array.from()创建了当前数组的新实例,并对该实例(而不是源)进行了更改。根据设计,React不处理可变状态(如果状态发生突变,它不会更新/渲染组件)。通过创建实例,我们用这个新的数组实例覆盖了旧的数组。
  • 使用localCompare()按指定的.sort()进行排序;例如,sortByType将按a[id].localCompare(b[id])的ID字符串按升序对a对象进行排序。通过反转此顺序,您可以获得降序:b
  • Object destructing是绕过点表示法b[id].localCompare(a[id])的简写,将是:const target = event.target;。我主要在const { target } = event;组件中使用它,在这里我从传递的属性中拉出Table{ property }
  • 使用酶的<Table {...this.state} />函数代替mount,因为mount显示了整个DOM树结构;而浅层仅显示根级DOM结构。

例如,shallow如下所示:

mount

<App> <div>example</div> <Child> <p>I'm a child!</p> <SecondChild> <p>I'm a secondary child</p> <SecondChild> <Child> </App> 如下所示:

shallow

由于我要同时针对根级和子级组件进行声明,因此我在<App> <div>example</div> <Child /> </App> 上使用mount -您可以通过使用酶shallow函数来查看此结构:{{ 1}}。

一些其他资源:

enzyme documentation

jest documentation