用于Redux表单组件的酶单元测试失败

时间:2019-05-23 14:57:32

标签: reactjs redux enzyme redux-form

我是前端测试的新手,并且对React生态系统非常了解,所以请多多包涵。

我已经对CustomerSection组件进行了一些单元测试。运行yarn spec时,出现以下错误:

➜  client git:(ConvertToRO_Test) ✗ yarn spec
yarn run v1.3.2
warning package.json: No license field
warning ../package.json: No license field
$ NODE_ENV=test mocha './spec/**/*.spec.js*' --compilers js:babel-register --recursive
Warning: Failed prop type: The prop `submitting` is marked as required in `Form`, but its value is `undefined`.
    in Form
Warning: Failed prop type: The prop `role` is marked as required in `Form`, but its value is `undefined`.
    in Form
Warning: Failed prop type: The prop `search` is marked as required in `Form`, but its value is `undefined`.
    in Form
Warning: Failed prop type: The prop `manager` is marked as required in `Form`, but its value is `undefined`.
    in Form
Warning: Failed prop type: The prop `id` is marked as required in `Form`, but its value is `undefined`.
    in Form
Warning: Failed prop type: The prop `close` is marked as required in `Form`, but its value is `undefined`.
    in Form
Warning: Failed prop type: The prop `handleSubmit` is marked as required in `Form`, but its value is `undefined`.
    in Form
Warning: Failed prop type: Invalid prop `fields` of type `array` supplied to `LineItemFields`, expected `object`.
    in LineItemFields
Warning: Failed prop type: The prop `company.name` is marked as required in `CompanyStatsTooltip`, but its value is `undefined`.
    in CompanyStatsTooltip (created by CustomerSection)
    in CustomerSection (created by ConnectedFields)
    in ConnectedFields (created by Connect(ConnectedFields))
    in Connect(ConnectedFields) (created by Fields)
    in Fields (created by WithFormFields)
    in WithFormFields (created by Form(WithFormFields))
    in Form(WithFormFields) (created by Connect(Form(WithFormFields)))
    in Connect(Form(WithFormFields)) (created by ReduxForm)
    in ReduxForm
    in Provider (created by WrapperComponent)
    in WrapperComponent
The above error occurred in the <CustomerSection> component:
    in CustomerSection (created by ConnectedFields)
    in ConnectedFields (created by Connect(ConnectedFields))
    in Connect(ConnectedFields) (created by Fields)
    in Fields (created by WithFormFields)
    in WithFormFields (created by Form(WithFormFields))
    in Form(WithFormFields) (created by Connect(Form(WithFormFields)))
    in Connect(Form(WithFormFields)) (created by ReduxForm)
    in ReduxForm
    in Provider (created by WrapperComponent)
    in WrapperComponent

Consider adding an error boundary to your tree to customize error handling behavior.
/Users/danielbonnell/Development/acme-api/client/node_modules/react-dom/cjs/react-dom.development.js:20106
    throw error;
    ^

TypeError: Cannot read property 'label' of undefined
    at /Users/danielbonnell/Development/acme-api/client/app/lib/forms.js:3:30
    at Array.map (<anonymous>)
    at toOptions (/Users/danielbonnell/Development/acme-api/client/app/lib/forms.js:2:27)
    at CustomerSection.render (/Users/danielbonnell/Development/acme-api/client/app/modules/SalesOrders/components/shared/FormSections/CustomerSection/CustomerSection.jsx:149:24)
    at finishClassComponent (/Users/danielbonnell/Development/acme-api/client/node_modules/react-dom/cjs/react-dom.development.js:14535:31)
    at updateClassComponent (/Users/danielbonnell/Development/acme-api/client/node_modules/react-dom/cjs/react-dom.development.js:14490:24)
    at beginWork (/Users/danielbonnell/Development/acme-api/client/node_modules/react-dom/cjs/react-dom.development.js:15438:16)
    at performUnitOfWork (/Users/danielbonnell/Development/acme-api/client/node_modules/react-dom/cjs/react-dom.development.js:19106:12)
    at workLoop (/Users/danielbonnell/Development/acme-api/client/node_modules/react-dom/cjs/react-dom.development.js:19146:24)
    at renderRoot (/Users/danielbonnell/Development/acme-api/client/node_modules/react-dom/cjs/react-dom.development.js:19229:7)
    at performWorkOnRoot (/Users/danielbonnell/Development/acme-api/client/node_modules/react-dom/cjs/react-dom.development.js:20136:7)
    at performWork (/Users/danielbonnell/Development/acme-api/client/node_modules/react-dom/cjs/react-dom.development.js:20048:7)
    at performSyncWork (/Users/danielbonnell/Development/acme-api/client/node_modules/react-dom/cjs/react-dom.development.js:20022:3)
    at requestWork (/Users/danielbonnell/Development/acme-api/client/node_modules/react-dom/cjs/react-dom.development.js:19891:5)
    at scheduleWork (/Users/danielbonnell/Development/acme-api/client/node_modules/react-dom/cjs/react-dom.development.js:19705:5)
    at scheduleRootUpdate (/Users/danielbonnell/Development/acme-api/client/node_modules/react-dom/cjs/react-dom.development.js:20366:3)
    at updateContainerAtExpirationTime (/Users/danielbonnell/Development/acme-api/client/node_modules/react-dom/cjs/react-dom.development.js:20394:10)
    at updateContainer (/Users/danielbonnell/Development/acme-api/client/node_modules/react-dom/cjs/react-dom.development.js:20451:10)
    at ReactRoot.render (/Users/danielbonnell/Development/acme-api/client/node_modules/react-dom/cjs/react-dom.development.js:20747:3)
    at /Users/danielbonnell/Development/acme-api/client/node_modules/react-dom/cjs/react-dom.development.js:20884:14
    at unbatchedUpdates (/Users/danielbonnell/Development/acme-api/client/node_modules/react-dom/cjs/react-dom.development.js:20253:10)
    at legacyRenderSubtreeIntoContainer (/Users/danielbonnell/Development/acme-api/client/node_modules/react-dom/cjs/react-dom.development.js:20880:5)
    at Object.render (/Users/danielbonnell/Development/acme-api/client/node_modules/react-dom/cjs/react-dom.development.js:20949:12)
    at Object.render (/Users/danielbonnell/Development/acme-api/client/node_modules/enzyme-adapter-react-16/build/ReactSixteenAdapter.js:352:114)
    at new ReactWrapper (/Users/danielbonnell/Development/acme-api/client/node_modules/enzyme/build/ReactWrapper.js:98:16)
    at mount (/Users/danielbonnell/Development/acme-api/client/node_modules/enzyme/build/mount.js:19:10)
    at Suite.<anonymous> (/Users/danielbonnell/Development/acme-api/client/spec/modules/SalesOrders/components/shared/FormSections/CustomerSection/CustomerSection.spec.jsx:26:19)
    at Object.create (/Users/danielbonnell/Development/acme-api/client/node_modules/mocha/lib/interfaces/common.js:114:19)
    at context.describe.context.context (/Users/danielbonnell/Development/acme-api/client/node_modules/mocha/lib/interfaces/bdd.js:44:27)
    at Object.<anonymous> (/Users/danielbonnell/Development/acme-api/client/spec/modules/SalesOrders/components/shared/FormSections/CustomerSection/CustomerSection.spec.jsx:10:1)
    at Module._compile (module.js:652:30)
    at loader (/Users/danielbonnell/Development/acme-api/client/node_modules/babel-register/lib/node.js:144:5)
    at Object.require.extensions.(anonymous function) [as .jsx] (/Users/danielbonnell/Development/acme-api/client/node_modules/babel-register/lib/node.js:154:7)
    at Module.load (module.js:565:32)
    at tryModuleLoad (module.js:505:12)
    at Function.Module._load (module.js:497:3)
    at Module.require (module.js:596:17)
    at require (internal/module.js:11:18)
    at /Users/danielbonnell/Development/acme-api/client/node_modules/mocha/lib/mocha.js:222:27
    at Array.forEach (<anonymous>)
    at Mocha.loadFiles (/Users/danielbonnell/Development/acme-api/client/node_modules/mocha/lib/mocha.js:219:14)
    at Mocha.run (/Users/danielbonnell/Development/acme-api/client/node_modules/mocha/lib/mocha.js:487:10)
    at Object.<anonymous> (/Users/danielbonnell/Development/acme-api/client/node_modules/mocha/bin/_mocha:459:18)
    at Module._compile (module.js:652:30)
    at Object.Module._extensions..js (module.js:663:10)
    at Module.load (module.js:565:32)
    at tryModuleLoad (module.js:505:12)
    at Function.Module._load (module.js:497:3)
    at Function.Module.runMain (module.js:693:10)
    at startup (bootstrap_node.js:191:16)
    at bootstrap_node.js:612:3
error Command failed with exit code 1.
info Visit https://yarnpkg.com/en/docs/cli/run for documentation about this command.

如果我使用shallow渲染,则测试将运行,但由于未渲染子组件而失败。我不确定是什么问题。

这是我的CustomerSection组件:

import React, { Component } from "react";
import T from "prop-types";
import { Row, Col } from "react-bootstrap";
import { Field } from "redux-form";
import api from "APP_ROOT/api";
import pojo from "LIB/pojos";
import { toOptions } from "LIB/forms";
import {
  Select,
  FormInput,
  CheckGroup,
  Check
} from "MODULES/shared/components/Forms";
import { CompanyStatsTooltip } from "MODULES/shared/components";
import AutoCompleteField from "MODULES/shared/components/Forms/AutoCompleteField/AutoCompleteField";
import {
  WIRE_IN_ADVANCE,
  CHECK_IN_ADVANCE,
  CREDIT_CARD,
  VENDOR_TERMS
} from "MODULES/companies/constants";
import * as F from "MODULES/shared/components/Forms/FormTypes";
import withFormFields from "MODULES/shared/decorators/withFormFields";

const baseOptions = [WIRE_IN_ADVANCE, CHECK_IN_ADVANCE, CREDIT_CARD];

class CustomerSection extends Component {
  state = {
    contacts: [],
    terms: baseOptions,
    selectedCompany: null,
    afterHours: this.props.afterHours
  };

  componentWillMount() {
    const { company, creditTerms } = this.props;

    if (company) {
      this.setState({ selectedCompany: company });
      this.searchContacts(company.id);
    }

    if (creditTerms) {
      this.addOption(creditTerms);
    }
  }

  searchCompanies = filter =>
    api.companies
      .search({ filter, page: 1, limit: 20 })
      .then(resp => resp.data.results);

  searchContacts = companyId => {
    if (!companyId) {
      this.setState({ contacts: [] });
    } else {
      api.companies.contacts
        .search(companyId, {
          active: true,
          page: 1,
          limit: 1000,
          sort: "name",
          reverse: false
        })
        .then(resp => {
          this.setState({ contacts: resp.data.results });
        });
    }
  };

  handleCompanySelected = company => {
    this.setState({ selectedCompany: company });
    this.resetTerms();
    this.props.contact_id.input.onChange(null);
    if (company.customer_credit_terms) {
      this.updateTerms(company.customer_credit_terms);
    }
    if (this.props.onCompanySelected) {
      this.props.onCompanySelected(company);
    }
    this.searchContacts(company.id);
  };

  resetTerms = () => {
    this.props.credit_terms.input.onChange(null);
    this.setState({ terms: baseOptions });
  };

  addOption = option => {
    this.setState({
      terms: baseOptions.concat(option)
    });
  };

  updateTerms = term => {
    this.addOption(term);
    this.props.credit_terms.input.onChange(term);
  };

  render() {
    const { contacts, terms } = this.state;
    const {
      canEditCompany = true,
      contact_id: { input: { value } }
    } = this.props;

    const options = pojo(VENDOR_TERMS).pick(...terms);
    const contactOptions = (contacts || [])
      .map(c => ({
        value: c.id,
        text: c.name
      }))
      .concat({
        text: "+ Add a Contact",
        value: "Add"
      });

    return (
      <div>
        <Row className="row-col-gutters-half">
          <Col md={8}>
            <Field
              name="company_id"
              label="Customer"
              labelChildren={
                this.state.selectedCompany && (
                  <React.Fragment>
                    &nbsp;&nbsp;<CompanyStatsTooltip
                      company={this.state.selectedCompany}
                    />
                  </React.Fragment>
                )
              }
              placeholder="Search by typing a company's name or code"
              component={AutoCompleteField}
              search={this.searchCompanies}
              formatItem={result => result && result.name}
              onItemSelected={i => i && this.handleCompanySelected(i)}
              selectedItem={this.state.selectedCompany}
              disabled={!canEditCompany}
              required
            />
          </Col>
          <Col md={4}>
            <Field
              name="credit_terms"
              component={Select}
              label="Customer Credit Terms"
              options={toOptions(options)}
              required
            />
          </Col>
        </Row>
        <Row className="row-col-gutters-half">
          <Col md={4}>
            <Field
              name="contact_id"
              component={Select}
              label="Customer Contact"
              options={contactOptions}
              required
            />
          </Col>
          <Col md={4}>
            <Field
              name="cc_email_address"
              component={FormInput}
              label="Contact Email #2"
            />
          </Col>
          <Col md={4}>
            <Field
              required
              name="customer_purchase_order_number"
              component={FormInput}
              label="Customer PO#"
            />
          </Col>
        </Row>
        {value === "Add" && (
          <div className="row row-col-gutters-half">
            <div className="col-md-6">
              <div className="form-group">
                <Field
                  name="contact.name"
                  component={FormInput}
                  label="Contact Name"
                  required
                />
              </div>
            </div>
            <div className="col-md-6">
              <div className="form-group">
                <Field
                  name="contact.email"
                  component={FormInput}
                  label="Contact Email"
                  required
                />
              </div>
            </div>
          </div>
        )}
        <Row className="row-col-gutters-half">
          <Col md={4}>
            <CheckGroup justified title="After Hours">
              <Field name="after_hours" component={Check} label="Yes" />
            </CheckGroup>
          </Col>
          <Col md={4}>
            <CheckGroup justified title="Send SO Acknowledgement">
              <Field
                name="send_sales_order_acknowledgement"
                component={Check}
                label="Yes"
                icon="fa-eye-slash text-success"
              />
            </CheckGroup>
          </Col>
        </Row>
      </div>
    );
  }
}

CustomerSection.propTypes = {
  company: T.shape({
    id: T.number.isRequired,
    customer_credit_terms: T.string
  }),
  creditTerms: T.string,
  onCompanySelected: T.func,
  credit_terms: F.formField(F.stringInput).isRequired,
  canEditCompany: T.bool,
  contact_id: F.formField(F.malleable).isRequired,
  afterHours: T.bool.isRequired
};

export default withFormFields(CustomerSection, [
  "credit_terms",
  "contact_id",
  "after_hours",
  "send_sales_order_acknowledgement"
]);

这是我的测试:

import React from "react";
import expect from "expect";
import { mount } from "enzyme";
import { createStore } from "redux";
import { Provider } from "react-redux";
import { Field, reduxForm } from "redux-form";
import { CheckGroup } from "MODULES/shared/components/Forms";
import CustomerSection from "MODULES/SalesOrders/components/shared/FormSections/CustomerSection/CustomerSection";

describe("<CustomerSection />", () => {
  const props = {
    company: {
      id: 1,
      customer_credit_terms: "Net-30"
    },
    creditTerms: "Net-30",
    onCompanySelected: () => {},
    canEditCompany: true,
    afterHours: true
  };

  const store = createStore(() => ({}));
  const DecoratedCustomerSection = reduxForm({ form: "testForm" })(
    CustomerSection
  );
  const wrapper = mount(
    <Provider store={store}>
      <DecoratedCustomerSection {...props} />
    </Provider>
  );

  afterEach(() => {
    expect.restoreSpies();
  });

  it("renders the correct Field components", () => {
    const fields = wrapper.find(Field);

    expect(fields.length).toEqual(9);

    expect(
      fields.findWhere(n => n.prop("label") === "Customer").length
    ).toEqual(1);
    expect(
      fields.findWhere(n => n.prop("label") === "Customer Credit Terms").length
    ).toEqual(1);
    expect(
      fields.findWhere(n => n.prop("label") === "Customer Contact").length
    ).toEqual(1);
    expect(
      fields.findWhere(n => n.prop("label") === "Contact Email #2").length
    ).toEqual(1);
    expect(
      fields.findWhere(n => n.prop("label") === "Customer PO#").length
    ).toEqual(1);
    expect(
      fields.findWhere(n => n.prop("label") === "Contact Name").length
    ).toEqual(1);
    expect(
      fields.findWhere(n => n.prop("label") === "Contact Email").length
    ).toEqual(1);
    expect(fields.findWhere(n => n.prop("label") === "Yes").length).toEqual(2);
  });

  it("renders the correct CheckGroup components", () => {
    const checkGroup = wrapper.find(CheckGroup);

    expect(
      checkGroup.findWhere(n => n.prop("title") === "After Hours").length
    ).toEqual(1);
    expect(
      checkGroup.findWhere(n => n.prop("title") === "Send SO Acknowledgement")
        .length
    ).toEqual(1);
  });
});

这是lib/forms.jstoOptions()的代码:

export function toOptions(map, textField = "label") {
  return Object.keys(map).map(t => {
    const text = textField ? map[t][textField] : map[t];
    return { value: t, text };
  });
}

0 个答案:

没有答案