所以Jest有一个非常棒的功能,允许在状态变化的结果中对组件进行动态重新渲染。它在网站上的React教程中进行了说明:
https://facebook.github.io/jest/docs/tutorial-react.html
以下是相关代码的简化版本:
test('Link changes the class when hovered', () => {
const component = renderer.create(
<Link page="http://www.facebook.com">Facebook</Link>
);
let tree = component.toJSON();
expect(tree).toMatchSnapshot();
// manually trigger the callback
tree.props.onMouseEnter();
// AUTOMATIC RE-RENDERING MID-TEST!!!!
tree = component.toJSON();
expect(tree).toMatchSnapshot();
});
这真棒,除了2017年编写React的惯用方法是尽可能少的有状态组件,这意味着使用Redux。但是,我似乎无法将上述内容与Redux配合使用。这就是我所拥有的:
export function renderWithStore(store, Component, mapStateToProps) {
return renderer.create(
<Provider store={store}>
<Component {...mapStateToProps(store.getState())} />
</Provider>
)
}
如果您想知道mapStateToProps
是什么,它是为测试组件生成道具而提供的功能(就像您使用connect
一样)。我有意将其调用移动到<Provider>
内嵌套,因为很明显,如果你只是通过一个带有它的道具的组件实例(通常会renderer.create
),它就不会# 39;能够响应商店中的状态变化。这就是组件工厂与其道具单独一起传递的原因。
为清楚起见,这里有mapStateToProps
的样子:
({total}) => {
return {total}
}
那么我们怎样才能实现&#34;活着&#34;我们可以在使用setState
时使用哪些组件?我们怎样才能使renderer.create
的回报继续响应还原状态变化?
也许,以某种方式调用forceUpdate
来回应store.subscribe
是解决方案?我现在要尝试一下。任何想法都会非常感激。
更新:
我将renderWithStore
函数更改为订阅并强制执行更新,如下所示:
export function renderWithStore(store, Component, mapStateToProps) {
let comp;
store.subscribe(() => {
comp.forceUpdate();
});
return renderer.create(
<Provider ref={c => comp = c} store={store}>
<Component {...mapStateToProps(store.getState())} />
</Provider>
)
}
它没有用。但它正在取得进展。正确调用强制更新,我在console.log()
内调用Component
来检查重新呈现是否正在发生。它实际上正在发生。但是,对component.toJSON
的调用似乎重新渲染了初始组件,而不是更新的组件,这仍然与它对setState
的响应方式不同。不知何故,似乎Jest在内部对setState
做了一个特殊的例外 - 尝试而不是forceUpdate
......
答案 0 :(得分:0)
好吧,我想通了,我想我想出了一些对使用redux和jest的反应开发者非常有用的东西。这是:
import React from 'react'
import renderer from 'react-test-renderer'
import {Provider} from 'react-redux'
import createHistory from 'history/createBrowserHistory'
import configureStore from '../src/configureStore'
import {loadSomethingAsync} from '../src/actions/async';
export async function createStore({shouldLoad=true}) {
const history = createHistory()
const store = configureStore(history)
if(shouldLoad) {
await store.dispatch(loadSomethingAsync())
}
return store
}
export function connect(store, Component) {
return (mapStateToProps) => {
let wrapper;
store.subscribe(() => {
wrapper.forceUpdate();
});
return renderer.create(
<Provider store={store}>
<Wrapper
ref={wr => wrapper = wr}
store={store}
Component={Component}
mapStateToProps={mapStateToProps}
/>
</Provider>
)
}
}
class Wrapper extends React.Component {
render() {
let {store, Component, mapStateToProps} = this.props
let props = typeof mapStateToProps === 'function'
? mapStateToProps(store.getState(), store.dispatch.bind(store))
: mapStateToProps //object or undefined
return <Component {...props} />
}
}
用法:
test('do something', async () => {
const store = await createStore()
const mapStateToProps = (state) => {
let {foo} = state
return {foo};
}
const render = connect(store, MyComponent) // reusable with
const component = render(mapStateToProps) // different props
let tree = component.toJSON()
expect(tree).toMatchSnapshot()
store.dispatch({type: 'SOMETHING_HAPPENED'}})
// or through a handler that does the same, eg: tree.props.onClick()
tree = component.toJSON()
expect(tree).toMatchSnapshot() // snapshot reflects state change :)
// now let's try re-using the enclosed component within
// our render function but with different mapStateToProps:
let tree2 = render({foo: 'bar'}); // you can also pass a plain object
expect(tree2).toMatchSnapshot()
});
就是这样。您的组件现在处于“活动状态”,即与redux同步,您不必连续呼叫renderer.create()
。 connect
基本上类似于您习惯使用的connect()
函数,但是组件和状态映射器的角色相反,允许您在不同的测试中重用相同的组件和不同的道具。