如何使用Jest和Enzyme在React-Native的单元测试中模拟事件

15

我正在尝试使用Jest在React-Native应用程序中测试"onPress"事件,以确保调用正确的函数。

我查阅了文档和谷歌,但在React-Native中找不到解决方案。

这是我发现的适用于React-Native的enzyme的方法:

const mockFunc = jest.fn();
const component = mount(<MyComponent onPress={mockFunc} />);
component.simulate('press');
expect(mockFunc).toHaveBeenCalled();

但这行不通。似乎mount不起作用,我得到了以下输出:

ReferenceError: document is not defined

我尝试使用shallow代替,但是当我查看函数的输出时并没有呈现出TouchableOpacity...你会猜到吧,这也不起作用。不知道该怎么办。

有人找到了在React-Native上测试事件的方法吗?

谢谢


1
我假设你使用enzyme,所以 p.simulate('press'); 应该可以工作。 - Andreas Köberle
酶“mount”似乎无法与React-Native一起使用,我不想使用“shallow”。“ReferenceError:document未定义” - alexmngn
你想测试什么类型的事件?它是否会改变“状态”?或者你到底想要测试什么?更多关于此的细节可能会有所帮助。 - Jason Gaare
@JasonGaare 我尝试测试一个 onPress 事件,并查看是否调用了我的函数。 - alexmngn
onPress 函数是在 MyComponent 上还是在 TouchableOpacity 上? - Jason Gaare
该组件使用TouchableOpacity包装了一个View。MyComponent基本上是一个按钮包装器,所以如果按下它,应该调用onPress函数。 - alexmngn
3个回答

14
酶不支持React-Native,因为它的渲染方式不同并且不使用DOM。这就是为什么您会收到错误ReferenceError:document is not defined。您可以查看此问题以获取更多信息。React团队目前正在努力公开一个.find()方法在react-test-renderer中,以模拟对组件的操作。然后,它应该适用于React / React-native,而不需要DOM环境。
有一个小技巧(这也是我们公司所做的)可以做到这一点,即渲染一个自定义组件来扩展TouchableOpacity并将onClick映射到调用onPress。像这样:
const mockPressable = (name) => {
  const RealComponent = require.requireActual(name);

  class Component extends RealComponent {

    render() {
      return React.createElement(
        RealComponent.displayName || RealComponent.name,
        { ...this.props, onClick: this.props.onPress },
        this.props.children
      );
    }

  }

  return Component;
};


jest.mock('TouchableOpacity', () => mockPressable('TouchableOpacity'));

在你的测试代码中,你调用了component.simulate('click')

这种做法有些取巧,我不确定这样做的后果如何,但对于我们的用例来说已经起到了作用。


谢谢你的回答!“React团队目前正在努力公开react-test-renderer中的.find()方法,以模拟对组件的操作。”有任何来源吗? - irrigator

11

你应该使用shallow,然后称为.dive()

const mockFunc = jest.fn();
const component = shallow(<MyComponent onPress={mockFunc} />);    
component.dive().simulate('press');
expect(mockFunc).toHaveBeenCalled();

这并不是解决一般问题的方法,因为你通常会使用mount而不是shallow。请参阅https://kentcdodds.com/blog/why-i-never-use-shallow-rendering - pipedreambomb

6
我可以在React Native中运行像您在问题中描述的测试。这是我的配置: package.json
"scripts": {
  ...
  "test": "node_modules/jest/bin/jest.js",
}

"devDependencies": {
  ...
  "enzyme": "^3.1.0",
  "enzyme-adapter-react-16": "^1.0.1",
  "enzyme-to-json": "^3.1.2",
  "jest": "^21.2.1",
  "jest-enzyme": "^4.0.0",
  "jest-expo": "~21.0.0",
}

"jest": {
  "preset": "jest-expo",
  "setupFiles": [
    "./test/jestSetup.js"
  ],
  "snapshotSerializers": [
    "./node_modules/enzyme-to-json/serializer"
  ]
}

test/jestSetup.js

import { configure, shallow, render, mount } from 'enzyme'
import Adapter from 'enzyme-adapter-react-16'

configure( { adapter: new Adapter() } )

// enzyme
global.shallow = shallow
global.render = render
global.mount = mount

示例组件:

import React from 'react'
import { Button } from 'react-native'

const CancelButton = ( props ) =>
  <Button
    { ...props }
    onPress={ () => { props.navigation.goBack() } }
    title="Cancel"
  />

export { CancelButton }

示例测试

import React from 'react'
import { CancelButton } from '../CancelButton'

test( 'onPress', () => {
  const goBackFunc = jest.fn()

  const navigation = {
    goBack: goBackFunc,
  }

  const component = shallow(
    <CancelButton
      navigation={ navigation }
    />
  )

  component.simulate( 'press' )
  expect( goBackFunc ).toHaveBeenCalled()
} )

.babelrc

{
  "presets": ["babel-preset-expo"],
  "env": {
    "development": {
      "plugins": ["transform-react-jsx-source"]
    }
  }
}

谢谢,使用 shallow 是一个不错的选择,对我也起作用。但是,使用 shallow 运行意味着搁置 React Native 的生命周期。想法是使用 mount 而不是 shallow 来使其工作。 - jose920405

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接