我很确定你的设置不起作用,因为你试图 shallow 挂载一个 redux 连接的组件——这是一个高阶组件 (HOC),它包装了另一个无法正确 @ 的组件987654326@d 由于酶的浅薄限制。
相反,您有两种选择:
选项 1.) 推荐:导出Dashboard 组件并使用shallow 或mount 对其进行断言(我主要使用mount 而不是shallow,专门用于避免过度和重复的.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.) 不推荐:用真正的store 和mount 连接的 HOC 将组件包装在真正的 Provider 中:
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
有关更多测试信息,请查看此answer,其中包含类似的示例(跳至要点;忽略 MemoryRouter 部分;虽然示例有点旧,但测试模式是一样)。
我推荐选项 1 而不是选项 2 的原因是它更易于管理,因为您直接操作正在测试的组件(而不是包装组件的 HOC)。选项 2 仅在您绝对需要测试 redux 到连接组件的整个工作流程时才有用。我发现选项 2 大多是矫枉过正,因为您可以单独对每个部分(动作、reducers 和未连接的组件)进行单元测试并实现相同的测试覆盖率。此外,正如我在 example 中提到的,我发现 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 包装器:
utils/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"