reactjs - 您如何调试浅渲染酶测试?
问题描述
我正在尝试修复我的 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 时,我什么也看不到?
解决方案
我很确定您的设置不起作用,因为您正在尝试shallow
安装一个 redux 连接的组件——这是一个高阶组件 (HOC),它包装了另一个dive
由于酶的浅层限制而无法正确插入的组件.
相反,您有两个选择:
选项 1.)推荐:导出Dashboard
组件并使用shallow
or对其进行断言mount
(我主要使用mount
overshallow
来避免过多和重复的.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
:用真实store
和mount
连接的 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"
推荐阅读
- python - 有没有办法从另一个 python 包中导入一个 python 包,就好像它是同一代码的一部分一样?
- python-3.x - Tkinter:pack()ing 使用 grid() 的帧
- android - 无法实例化应用程序 androidx.multidex.MultiDexApplication:java.lang.ClassNotFoundException
- r - 如何用 ggplot2 绘制长图例
- r - 基于多地分层预警系统,聚合每日数据,提供一份预警输出
- c# - 如何在 C# 中将文本文件写入可移动磁盘?
- java - 计算while循环中条件语句运行的次数
- javascript - 为什么最后一个函数没有返回你毫无疑问
- javascript - Promise 中的 Promise 队列
- python - 循环函数中的时间没有改变