嗨,这更多的是问题而不是问题。
我最近开始在项目中使用React和Redux,并且在测试中遇到了一些问题,但是文档似乎并未给出解决问题的最终解决方案。
目前,我有一个连接的组件,我想为其编写一些单元测试,但是在第一个组件中嵌套了一个连接的组件,而且我似乎最终被以下组件的属性或状态(或在大多数情况下都是)阻塞了未设置嵌套组件。
我想知道是否有一种方法可以对一个组件进行打桩,以便仅专注于要测试的组件?
提前致谢
两件事情:
1)这是用法,可能应该去堆栈溢出。
2)使用connect
高阶组件的部分便利
是您可以只为unconnected
组件编写单元测试,
信任redux来处理商店周围的管道
如果不清楚我的意思,我可以举个例子。
一些想法:
<Provider store={testStore}><ConnectedComponent /></Provider>
仅供参考,我在这里有许多关于React / Redux测试的文章的链接,您可能会发现有帮助: https :
是的,作为使用问题,在其他地方确实更好。
谢谢@markerikson,我目前正在尝试您提到的后一种方法,但也会为您的链接列表添加书签:)
我仍然认为,如果您想单独测试一次组件,那么最好做一些类似的事情
// Component.js
import React from 'react'
export const Component = ({ a, b, c }) => (
// ...
)
// ...
export default connect(mapStateToProps, mapDispatchToProps)(Component)
那么您就可以在测试中将组件连接之前引入它
// Component.spec.js
import { Component } from './Component.js'
import { mount } from 'enzyme'
it('should mount without exploding', () => {
mount(<Component a={1} b={2} c={3} />)
})
如果您真正想要测试的是商店的更改正在正确传播,则渲染Provider
是有意义的,那是不同的,尽管我不确定测试是否会带来很多价值。
这里引起关注的是,当被测试的组件然后呈现其他连接的组件时,它们是直接子级还是层次结构中的某个位置。 如果通过mount()
进行完整渲染,则后代连接的组件将绝对尝试访问this.context.store
,但找不到它。 因此,主要选择是通过进行浅层测试来确保未渲染后代,或者实际上使存储在上下文中可用。
@markerikson啊,好的,我只是重新读一遍,我以为OP正在尝试测试内部组件而不是外部组件。 👍
到目前为止,感谢您的帮助,
我目前遇到的主要问题是,我似乎无法将状态传递给嵌套在其中的已连接组件,因此视图无法正确呈现。
一般来说,模拟状态实际上是我一直在努力的事情,并且无法在该主题上找到任何结论性的东西,因此,如果有人可以给我一些有关如何将模拟状态传递给对象的想法,那将是很好的嵌套连接或更高阶的组件?
TIA
@StlthyLee :“通过状态”是什么意思? 如果在测试中渲染<Provider store={store}><ComponentWithConnectedDescendants /></Provider>
,那应该可以处理。
那就是问题所在,只是这样做不能正确地处理它,嵌套在其中的组件需要来自应用程序状态的特定参数,该参数目前由于某种原因或其他原因而无法获得。
@StlthyLee可能应该放一个示例/ gist / codepen / repo-link或其他内容,发现问题的时间会更少,因为此处似乎存在术语冲突。
@Koleok希望这将有助于澄清
https://gist.github.com/StlthyLee/02e116d945867a77c382fa0105af1bf3
如果不是的话,请问一下,因为这是我热衷于了解的东西,因为在Rails领域工作了9至10年,并且从这个角度对TDD有了很好的了解,所以我现在试图我在React / Redux世界中绕着TDD前进。
因此,我认为我现在已经到了这个问题的最深处,这不是测试问题,而是更多由另一位团队成员制作的副本和过去的错误,我想这是必须写测试以适合代码,而不是我偏爱的TDD方法。 感谢您在此提供的所有信息,我们非常重视
为了提供另一个适用于某些用例的选项,我最近遇到了一个嵌套连接组件的问题。 我没有建立模拟存储,而是导出了每个组件的非连接版本,以在没有存储的情况下对它们进行单元测试。 像这样:
class TopLevelImpl extends React.PureComponent {
// Implementation goes here
}
// Export here for unit testing. The unit test imports privates and uses TopLevel
// with Enzyme's mount().
export const privates = {
TopLevel: TopLevelImpl,
}
export const TopLevel = connect(mapStateToProps)(TopLevelImpl)
我对子嵌套组件做了同样的事情,因此每个组件都可以独立测试。
然后,我获取了顶级连接组件,并将子连接组件作为道具传递给应用程序的render()
函数。 但是,当对顶级组件进行单元测试时,我没有传递连接的组件,而是传递了模拟组件以确保将它们呈现在正确的位置。 您也可以传入未连接的组件。
像这样:
// Root render function
render() {
return (
<Provider store={store}>
<TopLevel child1={<Child1 />} child2={<Child2 />} />
</Provider>
)
}
我真的很喜欢你的想法。 但是我做这样的事情
const props = {
child1: () => (<div />),
child2: () => (<div />)
}
<TopLevel {...props} />
我认为,如果您已经测试过child1和child2,那么可能就不需要在TopLevel组件中使用它们了。
如果需要有条件地渲染组件,我也会传递一个函数。
但是我不听你说的最后一句话。 测试与将子组件传递到另一个组件有什么关系?
好。 例如,我有外部连接,它使用一个内部连接的组件。 在这种情况下,如果我想测试外部组件,我会像这样
wrapper = mount(<ExternalComponent.WrappedComponent {...props} />)
但是,我有一个错误,我的内部组件需要状态才能正常运行。
Invariant Violation: Could not find "store" in either the context or props
然后,我尝试通过模拟状态等。 但是就我而言,我必须对每个连接的组件进行很多初始化,包括异步调用,initialState等,并且还有与此相关的其他错误。
我认为如果我只想测试外部组件,那么对所有内部连接的组件进行所有正确的初始化将是一个过大的杀伤力。
因此,我决定将所有连接的组件移出并将它们作为道具传递给外部连接的组件。 现在,当我要测试时,我可以将它们替换为空的div 。
哦,是的,这只是该模式的原始动机。 因此,修改句子的意思是“我认为,如果您已经测试过child1和child2,那么在进行单元测试时就无需将它们传递到TopLevel组件中。”
同意
@StlthyLee ,不幸的是,当我尝试测试嵌套了连接组件的组件时,我遇到了同样的问题。
我发现一个特别有用的解决方案是根据测试环境将嵌套组件连接到redux存储。
NODE_ENV="test"
。 您可以捕获此变量并使组件接收默认属性。 在这种情况下,该组件未连接到存储,并且在测试父组件时不会出现任何问题。a)首先,您需要一个帮助程序函数,该函数确定环境是否为test
:
export default function ifTestEnv(forTestEnv, forNonTestEnv) {
const isTestEnv = process && process.env && process.env.NODE_ENV === 'test';
const hoc = isTestEnv ? forTestEnv : forNonTestEnv;
return function(component) {
return hoc(component);
};
}
b)接下来,应根据环境有条件地应用defaultProps()
和connect()
:
import React, { Component } from 'react';
import { connect } from 'react-redux';
import defaultProps from 'recompose/defaultProps';
import ifTestEnv from 'helpers/if_test_env';
import { fetch } from './actions';
class MyComponent extends Component {
render() {
const { propA, propB } = this.props;
return <div>{propA} {probB}</div>
}
componentDidMount() {
this.props.fetch();
}
}
function mapStateToProps(state) {
return {
propA: state.valueA,
propB: state.valueB
};
}
export default ifTestEnv(
defaultProps({
propA: 'defaultA',
propB: 'defaultB',
fetch() { }
}),
connect(mapStateToProps, { fetch })
)(MyComponent);
c)运行测试时,确保NODE_ENV
是"test"
:
NODE_ENV='test' mocha app/**/*.test.jsx
。plugins: [
// plugins...
new webpack.DefinePlugin({
'process.env': {
NODE_ENV: JSON.stringify('test')
},
})
]
@markerikson感谢您的两个建议。 我尝试了第二个组件,并包装了我的组件以使用提供程序进行测试。 在这种情况下, mapStateToProps
可以正常工作,但是mapDispatchToProps
不能获得预期的结果。 在这里,被测试的组件将渲染其他连接的组件。
我的mapDispatchToProps看起来像
/* <strong i="10">@flow</strong> */
import { bindActionCreators } from 'redux';
import type { Dispatch } from './types';
import * as actions from './actions';
export default (dispatch: Dispatch, ownProps: Object): Object => ({
actions: bindActionCreators(actions, dispatch),
});
这里返回的动作值为undefined
。
store.js
import { applyMiddleware, compose, createStore } from 'redux';
import { autoRehydrate } from 'redux-persist';
import thunk from 'redux-thunk';
import rootReducer from './reducers';
import { REHYDRATE } from 'redux-persist/constants';
const store = compose(autoRehydrate(), applyMiddleware(thunk, createActionBuffer(REHYDRATE)))(createStore)(rootReducer);
export default store;
我意识到这是一个很旧的线程,但是我发现这是一个令人讨厌的问题,并认为此解决方案可能会帮助偶然发现此线程的人(例如我)。
因为我实际上没有测试与Redux相关的任何内容,所以我发现一个简单的解决方案是模拟connect函数以仅返回传递给它的未更改组件。
例如,在您的应用程序中,您可能具有以下组件:
export class ExampleApp extends Component {
render() {
}
}
export default connect(
null,
{ someAction }
)(ExampleApp);
然后在测试文件的开头,您可以放置一个模拟的connect函数来停止Redux与您的组件进行交互:
jest.mock("react-redux", () => {
return {
connect: (mapStateToProps, mapDispatchToProps) => (
ReactComponent
) => ReactComponent
};
});
该模拟将需要放置在每个安装了连接子项的测试文件中。
@scottbanyard您如何将道具传递给子组件?
最有用的评论
这里引起关注的是,当被测试的组件然后呈现其他连接的组件时,它们是直接子级还是层次结构中的某个位置。 如果通过
mount()
进行完整渲染,则后代连接的组件将绝对尝试访问this.context.store
,但找不到它。 因此,主要选择是通过进行浅层测试来确保未渲染后代,或者实际上使存储在上下文中可用。