首页 > 解决方案 > 您如何调试浅渲染酶测试?

问题描述

我正在尝试修复我的 react-redux 应用程序中的失败测试。当我再次深入我的组件时,我希望在其中看到 JSX。但是,我什么也没看到。

这是我的组件-

      const Dashboard = (props) => {
          if (props.isSignedIn)
            return (
              <div className="dashboard">
                  <h1>Welcome</h1>
              </div>
            );
          return null;
        };

    const mapStateToProps = (state) => {
      return { isSignedIn: state.auth.isSignedIn };
    };
export default connect(mapStateToProps, { signIn, signOut })(Dashboard);

这是我失败的测试:-

const setup = (initialState = {}) => {
  const store = storeFactory(initialState);
  const wrapper = shallow(<Dashboard store={store} />).dive().dive();
  return wrapper;
};

describe("on render", () => {
describe("the user is signed in", () => {
  let wrapper;
  beforeEach(() => {
    const initialState = { isSignedIn: true };
    wrapper = setup(initialState);
  });
  it("renders the dashboard", () => {
    const component = wrapper.find("dashboard");
    expect(component.length).toBe(1);
  });
});

我的商店工厂:-

export const storeFactory = (initialState) => {
  const createStoreWithMiddleware = applyMiddleware(reduxThunk)(createStore);
  console.log(initialState);
  return createStoreWithMiddleware(rootReducer, initialState);
};

我的测试错误:-

● the user is signed in › renders the dashboard

    expect(received).toBe(expected) // Object.is equality

    Expected: 1
    Received: 0

当我潜水一次时,它看起来像这样:-

 <Dashboard store={{...}} isSignedIn={{...}} signIn={[Function]} signOut={[Function]} />}

但是当我尝试查看仪表板组件内部的 JSX 时,我什么也看不到?

标签: reactjstestingreduxreact-reduxenzyme

解决方案


我很确定您的设置不起作用,因为您正在尝试shallow安装一个 redux 连接的组件——这是一个高阶组件 (HOC),它包装了另一个dive由于酶的浅层限制而无法正确插入的组件.

相反,您有两个选择:

选项 1.)推荐:导出Dashboard组件并使用shallowor对其进行断言mount(我主要使用mountovershallow来避免过多和重复的.dive()调用)。

首先导出未连接的组件:

export const Dashboard = (props) => {
  if (props.isSignedIn)
    return (
      <div className="dashboard">
        <h1>Welcome</h1>
      </div>
    );

  return null;
}

export default connect(...)(Dashboard)

然后在您的测试中,导入 Dashboard 组件(不是默认的连接导出):

import { mount } from "enzyme";
import { Dashboard } from "./Dashboard"; // importing named export "Dashboard"

const initialProps = {
  isSignedIn: false
}

const wrapper = mount(<Dashboard { ...initialProps } />); // alternatively, you can use shallow here

// now manipulate the wrapper using wrapper.setProps(...);

或者

选项 2.)不推荐Provider:用真实storemount连接的 HOC将组件包装在真实中:

import { mount } from "enzyme";
import { Provider } from "redux";
import { createStore, applyMiddleware } from "redux";
import thunk from "redux-thunk";
import rootReducer from "../path/to/reducers";
import types from "../path/to/types";
import Dashboard from "./Dashboard"; // importing connected default export

const store = createStore(rootReducer, undefined, applyMiddleware(thunk));

const initialProps = {
  isSignedIn: false
};

const wrapper = mount(
  <Provider store={store}>
    <Dashboard { ...initialProps } />
  </Provider>
);

// now you'll dispatch real actions by type and expect the redux store to change the Dashboard component

有关更多测试信息,请查看此答案,其中包含一个类似的示例(跳至要点;忽略 MemoryRouter 部分;虽然示例有点旧,但测试模式是相同的)。

我推荐选项 1 而不是选项 2 的原因是它更易于管理,因为您直接操作正在测试的组件(而不是包装组件的 HOC)。选项 2 仅在您绝对需要测试 redux 到连接组件的整个工作流程时才有用。我发现选项 2 大多是矫枉过正,因为您可以单独对每个部分(动作、reducers 和未连接的组件)进行单元测试并实现相同的测试覆盖率。此外,正如我在这个例子中提到的,我发现redux-mock-store它对单元测试 redux 操作最有用。

在旁注中,您可以通过在测试中使用来查看酶的作用console.log(wrapper.debug());


示例未连接测试:

import React, { createElement } from "react";
import { configure } from "enzyme";
import Adapter from "enzyme-adapter-react-16";
import { mount } from "enzyme";
import { MemoryRouter } from "react-router-dom";
import { Dashboard } from "./App.js";

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

const initialProps = {
  isSignedIn: false
};

describe("Unconnected Dashboard Component", () => {
  let wrapper;
  beforeEach(() => {
    /* 
      This code below may be a bit confusing, but it allows us to use
      "wrapper.setProps()" on the root by creating a function that first
       creates a new React element with the "initialProps" and then
       accepts additional incoming props as "props". 
    */
    wrapper = mount(
      createElement(
        props => (
          <MemoryRouter initialEntries={["/"]}> // use memory router for testing (as recommended by react-router-dom docs: https://reacttraining.com/react-router/web/guides/testing)
            <Dashboard {...props} />
          </MemoryRouter>
        ),
        initialProps
      )
    );
  });

  it("initially displays nothing when a user is not signed in", () => {
    expect(wrapper.find(".dashboard").exists()).toBeFalsy();
  });

  it("displays the dashboard when a user is signed in", () => {
    wrapper.setProps({ isSignedIn: true });
    expect(wrapper.find(".dashboard").exists()).toBeTruthy();
  });
});

工作示例(单击Tests选项卡以运行测试):

编辑简单测试


可重复使用的 HOC 包装器:

实用程序/HOCWrapper/index.js

import React, { createElement } from "react";
import { createStore, applyMiddleware } from "redux";
import { Provider } from "react-redux";
import { MemoryRouter } from "react-router-dom";
import { mount } from "enzyme";
import thunk from "redux-thunk";
import rootReducer from './path/to/reducers';

const middlewares = applyMiddleware([thunk]);
export const store = createStore(rootReducer, null, middlewares);

/**
 * Factory function to create a mounted MemoryRouter + Redux Wrapper for a component
 * @function HOCWrapper
 * @param {node} Component - Component to be mounted
 * @param {object} initialProps - Component props specific to this setup.
 * @param {object} state - Component initial state for setup.
 * @param {array} initialEntries - Initial route entries for MemoryRouter.
 * @param {object} options - Optional options for enzyme's "mount"
 * @function createElement - Creates a wrapper around passed in component (now we can use wrapper.setProps on root)
 * @returns {MountedRouterWrapper}
 */
export const HOCWrapper = (
    Component,
    initialProps = {},
    state = null,
    initialEntries = ["/"],
    options = {},
) => {
    const wrapper = mount(
        createElement(
            props => (
                <Provider store={store}>
                    <MemoryRouter initialEntries={initialEntries}>
                        <Component {...props} />
                    </MemoryRouter>
                </Provider>
            ),
            initialProps,
        ),
        options,
    );
    if (state) wrapper.find(Component).setState(state);
    return wrapper;
};

export default HOCWrapper;

要使用它,请导入 HOCWrapper 函数:

import Component from "./Example";
import HOCWrapper from "./path/to/utils/HOCWrapper"; 
// if you need access to store, then... 
// import HOCWrapper, { store } from "./path/to/utils/HOCWrapper";

const initialProps = { ... };
const initialState = { ... }; // optional (default null)
const initialEntries = [ ... ]; // optional (default "/")
const options = { ... }; // optional (default empty object)

// use the JSDoc provided above for argument breakdowns
const wrapper = HOCWrapper(Component, initialProps, initialState, initialEntries, options); // not all args are required, just the "Component"

推荐阅读