我发现Jest在测试我的Redux React应用程序时非常有用。但是,有很多关于如何测试异步操作创建器的示例,但我无法弄清楚如何对异步组件进行快照。
我想做的是与hovered link example from Facebook's own tutorial类似的事情。他们在onMouseEnter()上调用props函数,然后拍摄快照。如果onMouseEnter()调度使用Redux Thunk创建的异步操作,是否有一种简单的方法可以做到这一点?
这就是我的thunk的样子,它使用了axios。
// test-api.js
export function getLinkInfo() {
return function(dispatch) {
return axios.get('/api/get-link-info')
.then(response => {
dispatch(getLinkInfoSuccess(response.data));
return response;
});
};
}
这是我自己的链接组件。
import React from 'react';
import { connect } from 'react-redux';
import * as api from '../../api/test-api';
class Link extends React.Component {
render() {
return (
<a href='#' onMouseEnter={this.props.getLinkInfo}>
Hover me
</a>
<div>{this.props.linkInfo}</div>
);
}
}
const mapDispatchToProps = function(dispatch) {
return {
getLinkInfo: function() {
dispatch(api.getLinkInfo());
}
}
}
const mapStateToProps = function(store) {
return {
linkInfo: store.getIn(['testState', 'linkInfo'], "")
};
};
export default connect(mapStateToProps, mapDispatchToProps)(Link);
最后是测试文件。
import React from 'react';
import Link from '../link';
import renderer from 'react-test-renderer';
test('Link changes linkInfo when hovered', () => {
const component = renderer.create(
<Link></Link>
);
let tree = component.toJSON();
expect(tree).toMatchSnapshot();
// manually trigger the callback
tree.props.onMouseEnter();
// re-rendering
tree = component.toJSON();
expect(tree).toMatchSnapshot();
});
答案 0 :(得分:1)
问题在于,当你想测试异步内容时,你需要在测试中使用promise的实例,要么从测试中返回它,所以jest知道它并且可以等待它,或者在测试中使用async await它自我(docs)。
你可以做的是模拟测试中的api:
从&#39;路径/导入{getLinkInfo}到/ / / api&#39; jest.mock(&#39; path / to / the / api&#39;,()=&gt;({ getLinkInfo:jest.fn() }))
这将使用具有getLinkInfo
间谍的对象覆盖模块。然后导入模块,以便在测试中设置间谍的实际实现。
test('Link changes linkInfo when hovered', () = > {
//create a new promise that can be returned from your test
const p = new Promise((resolve) = > {
//set the spy to make the request and resolve the promise
getInfo.mockImplementation(function (dispatch) {
return axios.get('/api/get-link-info')
.then(response = > {
dispatch(getLinkInfoSuccess(response.data));
resolve(response);
});
};)
};)
const component = renderer.create(
<Link></Link>
);
let tree = component.toJSON();
expect(tree)
.toMatchSnapshot();
// manually trigger the callback
tree.props.onMouseEnter();
return p.then(() = > {
tree = component.toJSON();
expect(tree)
.toMatchSnapshot()
})
});
虽然这可以解决您的实际问题,但我建议不要像这样运行您的测试,调用您的真实API,还要自行模拟请求。首先,您的测试速度会快得多,并且不依赖于某些正在运行的后端。
关键是你要将React组件作为一个单元进行测试,因此它不关心调用getLinkInfo
后发生的事情。这些是getLinkInfo
的单元测试的详细信息。你的组件知道的是,它调用getLinkInfo
传递一个回调,有时会调用这个回调。当它被调用时,其间发生的事情不是组件责任的一部分。如果您考虑这样的测试,最简单的解决方案是立即调用回调。
test('Link changes linkInfo when hovered', () = > {
getInfo.mockImplementation(function (dispatch) {
dispatch({
some: 'Data'
});
};)
const component = renderer.create(
<Link></Link>
);
let tree = component.toJSON();
expect(tree)
.toMatchSnapshot();
// manually trigger the callback
tree.props.onMouseEnter();
tree = component.toJSON();
expect(tree).toMatchSnapshot()
});
答案 1 :(得分:0)
在教程中,他们有一个有状态的组件。这需要进行这样的“体操”。
对于一个纯粹的无状态组件,你应该只测试两件事:
props
,但是,您只导出connect
生成的HOC。您可以通过导出两者(以及mapDispatchToProps
和mapStateToProps
)来解决此问题。或者,通过模拟connect
使其返回测试的原始组件。
该文件如下所示:
import …
export class Link extends React.Component {
…
}
export const mapDispatchToProps = …
export const mapStateToProps = …
export default connect(mapStateToProps, mapDispatchToProps)(Link);
测试:
import …
import { Link, mapDispatchToProps, mapStateToProps } from './Link'
test('renders correctly', () => {
const tree = renderer.create(
<Link linkInfo="Link info" />
).toJSON()
expect(tree).toMatchSnapshot()
})
test('calls getLinkInfo', () => {
const getLinkInfo = jest.fn()
const tree = renderer.create(
<Link getLinkInfo={getLinkInfo} />
)
tree.props.onMouseEnter()
expect(getLinkInfo).toHaveBeenCalled()
})
test('mapDispatchToProps', () => … )
test('mapStateToProps', () => … )
这是纯组件的完整测试。
问题的第二部分是关于测试异步动作创建者。棘手的部分是axios
。它从何而来?我假设你将它导入顶部。所以你必须嘲笑它 - 呃,很快就会变得混乱。
有一个较小的知道extraArgument你可以传递给redux thunk。这可以作为纯依赖注入工作,这使得动作创建者很容易测试。
像这样使用:
const store = createStore(
reducer,
applyMiddleware(thunk.withExtraArgument({ axios }))
)
然后将此依赖项(或更多,如果需要)作为第三个参数传递给thunk:
export function getLinkInfo() {
return function(dispatch, getState, { axios }) {
return axios.get('/api/get-link-info')
.then(response => {
dispatch(getLinkInfoSuccess(response.data));
return response;
});
};
}
现在很酷。异步动作创建者的测试:
import * as actions from './actions'
describe('getLinkInfo', () => {
const action = actions. getLinkInfo()
const dispatch = jest.fn()
const getState = () => { … }
const axios = {
get: jest.fn(() => Promise.resolve({
data: {}
}))
}
beforeEach(() => {
deps.axios.get.mockClear()
})
test('fetches info from the server', () => {
action(dispatch, getState, { axios })
expect(axios.get).toHaveBeenCalledTimes(1)
expect(axios.get.mock.calls).toMatchSnapshot()
})
})
P.S。我在这里展示了这些和一些更好的Jest测试模式: https://github.com/robinpokorny/jest-example-hannoverjs