用Jest和Enzyme测试React组件

发表于:2019-4-29 13:11

字体: | 上一篇 | 下一篇 | 我要投稿

 作者:Lingzhi    来源:掘金

分享:
  前言
  测试是应用生产过程中不可缺少的一个环节,开发人员在编码时总有考虑不周全或者出错的情况,而测试则是通过对比实际结果与预期结果来找出问题和缺陷,从而确保软件的质量。本文主要介绍了在最近在工作中用Jest和Enzyme来测试React 组件的过程和容易踩坑的地方。
  测试种类
  对于一个Web网站来说,测试的种类主要分为以下3种:
  单元测试: 测试单个函数或者类,提供输入,确保输出和预期的一样。单元测试的粒度要尽可能小,不要考虑其他类和模块的实现。
  集成测试: 测试整个流程或者某组件能够按预期的运行,用来覆盖跨模块的过程。同时也要包括一些反面用例。
  功能测试: 站在产品的角度测试各个场景,通过操作浏览器或者网站,忽略内部实现细节和结构,确保和预期的行为一样。
  测试框架
  市面上现在有很多测试工具,公司里采用Umijs作为脚手架快速搭建了一个React应用,而Umi内部采用了Dva作为数据流管理,同时也自动配置了Jest测试框架。
  Jest测试框架由Facebook所推荐,其优点是运行快性能好,并且涵盖了测试所需的多种功能模块(包括断言,模拟函数,比较组件的快照snapshot,运行测试,生成测试结果和覆盖率等),配置简单方便,适合大项目的快速测试。
  React组件的测试
  测试React组件我们采用Enzyme工具库,它提供3种组件渲染方式:
  Shallow:不会渲染子组件
  Mount: 渲染子组件,同时包含生命周期函数如componentDidMount
  Render: 渲染子组件,但不会包含生命周期,同时可用的API也会减少比如setState()
  一般情况下用shallow和mount的情况比较多。
  被Connect包裹的组件
  有些组件被Connect包裹起来,这种情况不能直接测,需要建立一个Provider和传入一个store,这种过程比较痛苦,最好是将去掉Connect后的组件 export出来单独测,采用shallow的渲染方法,仅测该组件的逻辑。
  例如被测的组件如下:
   export class Dialog extends Component {
  ...
  }
  export default connect(mapStateToProps, mapDispatch)(Dialog)
  那么在测试文件中, 可以这样初始化一个控件:
   import {Dialog} from '../dialog'
  function setup(propOverrides) {
  const props = Object.assign(
  {
  state:{}
  actions:{},
  },
  propOverrides,
  )
  const enzymeWrapper = shallow(<Dialog  {...props} />)
  return {
  props,
  enzymeWrapper,
  }
  }
  需和子组件和原生DOM元素交互的组件
  有的组件,需要测试和原生DOM元素的交互,比如要测点击原生button元素,是否触发当前的组件的事件,或者需要测试和子组件的交互时,这时候用需要用mount来渲染。
  例如,我的Editor组件是这样:
   export default class Editor extends Component {
  constructor(props) {
  super(props)
  this.state = {
  onClickBtn: null,
  }
  }
  handleSubmit = ({ values, setSubmitting }) => {
  const { onClickBtn } = this.state
  this.props.actions.createInfo(values, onClickBtn)
  }
  handleCancel = () => {
  ...
  }
  setOnClickBtn(name) {
  this.setState({
  onClickBtn: name,
  })
  }
  render() {
  return (
  <Form onSubmit={this.handleSubmit}>
  {({ handleChange }) => {
  return (
  <div className="information-form">
  <Input name={FIELD_ROLE_NAME} onChange={handleChange}
  />
  <Input name={FIELD_ROLE_KEY} onChange={handleChange}
  />
  <div>
  <Button type="button" onClick={this.handleCancel}> Cancel </Button>
  <Button type="submit" primary onClick={() => this.setOnClickBtn('create')} > Create </Button>
  <Button type="submit" primary onClick={() => this.setOnClickBtn('continue')} > Create and Continue </Button>}
  </div>
  </div>
  )
  }}
  </Form>
  )
  }
  }
  此时Form的children是个function,要测试表单中按钮点击事件,如果只用shallow,是无法找到Form中children的元素的,因此这里采用mount方式将整个dom渲染,可直接模拟type为submit属性的那个button的点击事件。
  然后测试点击该button是否完成了2个事件:handleSubmit和setOnclickBtn。
  有人会想到模拟form的submit事件,但在mount的情况下,模拟button的click事件同样可以触发onSubmit事件。
  由于submit过程要涉及子控件的交互,其过程具有一定的不确定性,此时需要设置一个timeout,延长一段时间再来判断submit内的action是否被执行。
   it('should call create role action when click save', () => {
  const preProps = {
  actions: {
  createInfo: jest.fn(),
  }
  }
  const { props, enzymeWrapper } = setup(preProps)
  const nameInput = enzymeWrapper.find('input').at(0)
  nameInput.simulate('change', { target: { value: 'RoleName' } })
  const keyInput = enzymeWrapper.find('input').at(1)
  keyInput.simulate('change', { target: { value: 'RoleKey' } })
  const saveButton = enzymeWrapper.find('button[type="submit"]').at(0)
  saveButton.simulate('click')
  expect(enzymeWrapper.state().onClickBtn).toBe('save')
  setTimeout(() => {
  expect(props.actions.createInfo).toHaveBeenCalled()
  }, 500)
  })
  但是用mount来渲染也有容易让人失误的地方,比如说要找到子组件,可能需要多层.children()才能找到。在单元测试中,应尽量采用shallow渲染,测试粒度尽可能减小。
  含有Promise的情况
  有的组件的函数逻辑中会含有Promise,其返回结果带有不确定性,例如以下代码段中的auth.handleAuthenticateResponse,传入的参数是一个callback函数,需要根据auth.handleAuthenticateResponse的处理结果是error还是正常的result来处理自己的内部逻辑。
   handleAuthentication = () => {
  const { location, auth } = this.props
  if (/access_token|id_token|error/.test(location.search)) {
  auth.handleAuthenticateResponse(this.handleResponse)
  }
  }
  handleResponse = (error, result) => {
  const { auth } = this.props
  let postMessageBody = null
  if (error) {
  postMessageBody = error
  } else {
  auth.setSession(result)
  postMessageBody = result
  }
  this.handleLogicWithState(postMessageBody)
  }
  在测试时,可用jest.fn()模拟出auth.handleAuthenticateResponse函数,同时让它返回一个确定的结果。
   const preProps = {
  auth: {
  handleAuthenticateResponse: jest.fn(cb => cb(errorMsg))
  }
  }
  setup(preProps)
  
      上文内容不用于商业目的,如涉及知识产权问题,请权利人联系博为峰小编(021-64471599-8017),我们将立即处理。
100家互联网大公司java笔试题汇总,填问卷领取~

关注51Testing

联系我们

快捷面板 站点地图 联系我们 广告服务 关于我们 站长统计 发展历程

法律顾问:上海兰迪律师事务所 项棋律师
版权所有 上海博为峰软件技术股份有限公司 Copyright©51testing.com 2003-2024
投诉及意见反馈:webmaster@51testing.com; 业务联系:service@51testing.com 021-64471599-8017

沪ICP备05003035号

沪公网安备 31010102002173号