在 React 和 Redux 中使用 Enzyme 进行嵌套组件测试
Posted
技术标签:
【中文标题】在 React 和 Redux 中使用 Enzyme 进行嵌套组件测试【英文标题】:Nested components testing with Enzyme inside of React & Redux 【发布时间】:2016-10-14 09:39:47 【问题描述】:我有一个组件SampleComponent
,它安装了另一个“连接组件”(即container
)。当我尝试通过mount
ing 测试SampleComponent
时(因为我需要componentDidMount
),我得到了错误:
不变违规:在上下文中找不到“商店”或 “连接(容器组件)”的道具。要么包装根组件 在 a 中,或明确地将“store”作为道具传递给 “连接(容器组件)”。
最好的测试方法是什么?
【问题讨论】:
从 Enzyme v3 开始,浅调用 ComponentDidMount github.com/airbnb/enzyme/blob/master/docs/api/shallow.md FYI 【参考方案1】:还有使用redux-mock-store的选项。
用于测试 Redux 异步操作创建者和中间件的模拟商店。模拟存储将创建一个调度的动作数组,作为测试的动作日志。
mock store 在 store 对象上提供了 Redux 所需的必要方法。 您可以指定可选的中间件和您的应用特定的初始状态。
import configureStore from 'redux-mock-store'
const middlewares = []
const mockStore = configureStore(middlewares)
const initialState =
const store = mockStore(initialState)
const wrapper = mount(<SampleComponent store=store/>)
【讨论】:
【参考方案2】:为了使装饰器语法的使用更具可测试性,我做了这个: https://www.npmjs.com/package/babel-plugin-undecorate
输入:
@anyOldClassDecorator
export class AnyOldClass
@anyOldMethodDecorator
method()
console.log('hello');
输出:
@anyOldClassDecorator
export class AnyOldClass
@anyOldMethodDecorator
method()
console.log('hello');
export class __undecorated__AnyOldClass
method()
console.log('hello');
希望这可以提供一个可靠的选项 3!
【讨论】:
【参考方案3】:选项 1)
您可以在测试中使用 React-Redux 的 Provider 组件包装容器组件。因此,使用这种方法,您实际上引用了 store,将其传递给 Provider,并在内部编写您的测试组件。这种方法的优点是您实际上可以为测试创建自定义存储。如果您想测试组件中与 Redux 相关的部分,这种方法很有用。
选项 2)
也许你不关心测试与 Redux 相关的部分。如果您只是对测试组件的渲染和本地状态相关的行为感兴趣,您可以简单地为组件的未连接纯版本添加一个命名导出。并且只是为了澄清当您将“export”关键字添加到您的类时,您基本上是在说现在可以通过两种方式导入该类,或者使用大括号 或不使用。示例:
export class MyComponent extends React.Component render() ...
...
export default connect(mapStateToProps, mapDispatchToProps)(MyComponent)
稍后在您的测试文件中:
import MyComponent from 'your-path/MyComponent'; // it needs a store because you use "default export" with connect
import MyComponent from 'your-path/MyComponent'; // don't need store because you use "export" on top of your class.
希望对大家有所帮助。
【讨论】:
【参考方案4】:您可以使用名称导出来解决这个问题:
你应该有:
class SampleComponent extends React.Component
...
render()
<div></div>
export default connect(mapStateToProps, mapDispatchToProps)(SampleComponent)
您可以在课前添加导出:
export class SampleComponent extends React.Component
并在没有 redux 存储的情况下导入此组件:
import SampleComponent from 'your-path/SampleComponent';
使用此解决方案,您无需将商店导入测试文件。
【讨论】:
【参考方案5】:Enzyme 的装载采用可选参数。您需要的两个是必要的
options.context: (Object [optional]): Context to be passed into the component
options.childContextTypes: (Object [optional]): Merged contextTypes for all children of the wrapper
您可以使用这样的选项对象挂载SampleComponent
:
const store =
subscribe: () => ,
dispatch: () => ,
getState: () => ( ... whatever state you need to pass in ... )
const options =
context: store ,
childContextTypes: store: React.PropTypes.object.isRequired
const _wrapper = mount(<SampleComponent ...defaultProps />, options)
现在您的 SampleComponent 会将您提供的上下文传递给 connected component
。
【讨论】:
这太完美了!虽然公认的答案在大多数情况下都有效,但缺点是您无法充分利用 mount api。例如,使用将组件包装在Provider
中的公认答案将不允许使用wrapper.state()
api。该解决方案将为您的包装器提供全方位的方法。
这是一个比接受的答案更好的答案,原因如上所述(即您安装的包装器实际上不是您要测试的组件),并且还因为您可以使用模拟商店而不是您的实际商店,将所有 redux 排除在外。
文档中没有这个可选参数,你是怎么找到的?在代码中?
如果SampleComponent
使用一个名为SubComponent
的连接组件,并且我们想检查它是否使用mount
正确呈现(集成测试,而不是单元)?您可以将存储作为第二个参数传递给SampleComponent
,但SubComponent
将没有存储。【参考方案6】:
我所做的基本上是引入我的redux
存储(和Provider
)并将其包装在一个实用程序组件中,如下所示:
export const CustomProvider = ( children ) =>
return (
<Provider store=store>
children
</Provider>
);
;
然后,我 mount
SampleComponent
并针对它运行测试:
it('contains <ChildComponent/> Component', () =>
const wrapper = mount(
<CustomProvider>
<SampleComponent ...defaultProps />
</CustomProvider>
);
expect(wrapper.find(ChildComponent)).to.have.length(1);
);
【讨论】:
我看到你正在使用 mount,如果我尝试用mount
替换 shallo
我会收到错误消息。你也遇到过吗?
虽然此答案在某些情况下有效,但当您需要测试组件的生命周期时它不起作用。例如,调用wrapper.setProps()
不会在SampleComponent
上触发componentWillReceiveProps()
。以上是关于在 React 和 Redux 中使用 Enzyme 进行嵌套组件测试的主要内容,如果未能解决你的问题,请参考以下文章
React Native 已经有了异步存储。为啥我应该在我的 react native 应用中使用 Redux 和 Redux Thunk?
使用 connect 与 react-redux 和 redux-persist
react中数据管理:react-redux和redux的使用