调用 Jest spyOn 函数

IT技术 javascript reactjs testing jestjs enzyme
2021-04-05 11:26:21

我正在尝试为一个简单的 React 组件编写一个简单的测试,并且我想使用 Jest 来确认当我用酶模拟点击时已经调用了一个函数。根据 Jest 文档,我应该可以使用spyOnspyOn

然而,当我尝试这个时,我不断得到TypeError: Cannot read property '_isMockFunction' of undefined我认为我的间谍未定义的意思。我的代码如下所示:

import React, { Component } from 'react';
import logo from './logo.svg';
import './App.css';
class App extends Component {

  myClickFunc = () => {
      console.log('clickity clickcty')
  }
  render() {
    return (
      <div className="App">
        <div className="App-header">
          <img src={logo} className="App-logo" alt="logo" />
          <h2>Welcome to React</h2>
        </div>
        <p className="App-intro" onClick={this.myClickFunc}>
          To get started, edit <code>src/App.js</code> and save to reload.
        </p>
      </div>
    );
  }
}

export default App;

在我的测试文件中:

import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import { shallow, mount, render } from 'enzyme'

describe('my sweet test', () => {
 it('clicks it', () => {
    const spy = jest.spyOn(App, 'myClickFunc')
    const app = shallow(<App />)
    const p = app.find('.App-intro')
    p.simulate('click')
    expect(spy).toHaveBeenCalled()
 })
})

任何人都知道我做错了什么?

3个回答

除了你的方式之外,你几乎没有任何变化spyOn当您使用的间谍,你有两个选择:spyOnApp.prototype或组件component.instance()


const spy = jest.spyOn(Class.prototype, "method")

在类原型上附加间谍和渲染(浅渲染)实例的顺序很重要。

const spy = jest.spyOn(App.prototype, "myClickFn");
const instance = shallow(<App />);

App.prototype第一行位有你需要的东西,使事情的工作。JavaScriptclass没有任何方法,除非您使用 实例化它new MyClass(),或者您使用MyClass.prototype. 对于您的特定问题,您只需要监视App.prototype方法myClickFn


jest.spyOn(component.instance(), "方法")

const component = shallow(<App />);
const spy = jest.spyOn(component.instance(), "myClickFn");

此方法需要 a 的shallow/render/mount实例React.Component可用。本质spyOn上只是在寻找可以劫持并推入jest.fn(). 它可能是:

一个平原object

const obj = {a: x => (true)};
const spy = jest.spyOn(obj, "a");

class

class Foo {
    bar() {}
}

const nope = jest.spyOn(Foo, "bar");
// THROWS ERROR. Foo has no "bar" method.
// Only an instance of Foo has "bar".
const fooSpy = jest.spyOn(Foo.prototype, "bar");
// Any call to "bar" will trigger this spy; prototype or instance

const fooInstance = new Foo();
const fooInstanceSpy = jest.spyOn(fooInstance, "bar");
// Any call fooInstance makes to "bar" will trigger this spy.

或者React.Component instance

const component = shallow(<App />);
/*
component.instance()
-> {myClickFn: f(), render: f(), ...etc}
*/
const spy = jest.spyOn(component.instance(), "myClickFn");

或者React.Component.prototype

/*
App.prototype
-> {myClickFn: f(), render: f(), ...etc}
*/
const spy = jest.spyOn(App.prototype, "myClickFn");
// Any call to "myClickFn" from any instance of App will trigger this spy.

我已经使用并看到了这两种方法。当我有一个beforeEach()orbeforeAll()块时,我可能会采用第一种方法。如果我只需要一个快速间谍,我会使用第二个。只需注意附加间谍的顺序即可。


编辑:如果你想检查你的副作用,myClickFn你可以在单独的测试中调用它。

const app = shallow(<App />);
app.instance().myClickFn()
/*
Now assert your function does what it is supposed to do...
eg.
expect(app.state("foo")).toEqual("bar");
*/

编辑:这是使用功能组件的示例。请记住,在您的功能组件范围内的任何方法都不可用于监视。您将监视传递到您的功能组件中的功能props并测试它们的调用。本实施例中探讨了使用的jest.fn(),而不是jest.spyOn,这两者的共享模拟函数API。虽然它没有回答最初的问题,但它仍然提供了对其他技术的见解,这些技术可以适用于与问题间接相关的案例。

function Component({ myClickFn, items }) {
   const handleClick = (id) => {
       return () => myClickFn(id);
   };
   return (<>
       {items.map(({id, name}) => (
           <div key={id} onClick={handleClick(id)}>{name}</div>
       ))}
   </>);
}

const props = { myClickFn: jest.fn(), items: [/*...{id, name}*/] };
const component = render(<Component {...props} />);
// Do stuff to fire a click event
expect(props.myClickFn).toHaveBeenCalledWith(/*whatever*/);
“Or a React.Component.prototype之后的代码块是否展示了与第一个代码块不同的内容?
2021-05-26 11:26:21
@Byrd 我不确定你的意思。玩笑是不起作用,spyOn不起作用还是其他什么?package.json是否正确配置了 jest 的配置方式?关于你的陈述的问题太多了。
2021-06-03 11:26:21
@VictorCarvalho 这种技术不太适合功能组件。这里的目标是监视类方法,功能组件没有。我建议研究testing-library/react您的用例。
2021-06-03 11:26:21
<class>.prototype!!你救了我的一天!!
2021-06-18 11:26:21
@AlexYoung 被监视的方法是任意的。获取方法的路径是任意的。示例代码有一个缺陷,它已得到解决。最终,有人会使用spyOn不接受 的props组件或不接受state. 间谍prototype对我来说是 100% 成功的。
2021-06-21 11:26:21

你快到了。尽管我同意 @Alex Young 关于为此使用props的回答,但instance在尝试监视该方法之前,您只需要参考

describe('my sweet test', () => {
 it('clicks it', () => {
    const app = shallow(<App />)
    const instance = app.instance()
    const spy = jest.spyOn(instance, 'myClickFunc')

    instance.forceUpdate();    

    const p = app.find('.App-intro')
    p.simulate('click')
    expect(spy).toHaveBeenCalled()
 })
})

文档:http : //airbnb.io/enzyme/docs/api/ShallowWrapper/instance.html

失踪forceUpdate让我们知道了......虽然看起来很奇怪,谁能解释一下?
2021-05-23 11:26:21
奇怪.. 我无法让上述内容用于类似的测试,但将应用程序渲染方法从“浅”更改为“挂载”修复了它。任何想法为什么这可能是修复/为什么此测试也不需要“安装”?
2021-05-30 11:26:21
知道为什么在我们强制更新时会起作用:O。那是超级怪异!
2021-06-06 11:26:21
@youngrrrr 可能您的函数依赖于 DOM,而 DOM 不会产生,而 mount 是完整的 DOM 渲染
2021-06-12 11:26:21
在调用模拟点击之前,调用 forceUpdate 将 spy 函数附加到实例: instance.forceUpdate()
2021-06-15 11:26:21

在您的测试代码中,您试图传递App给 spyOn 函数,但 spyOn 仅适用于对象,而不适用于类。通常,您需要在此处使用以下两种方法之一:

1)点击处理程序调用作为props传递的函数,例如

class App extends Component {

  myClickFunc = () => {
      console.log('clickity clickcty');
      this.props.someCallback();
  }
  render() {
    return (
      <div className="App">
        <div className="App-header">
          <img src={logo} className="App-logo" alt="logo" />
          <h2>Welcome to React</h2>
        </div>
        <p className="App-intro" onClick={this.myClickFunc}>
          To get started, edit <code>src/App.js</code> and save to reload.
        </p>
      </div>
    );
  }
}

您现在可以将 spy 函数作为 prop 传递给组件,并断言它被调用:

describe('my sweet test', () => {
 it('clicks it', () => {
    const spy = jest.fn();
    const app = shallow(<App someCallback={spy} />)
    const p = app.find('.App-intro')
    p.simulate('click')
    expect(spy).toHaveBeenCalled()
 })
})

2)点击处理程序在组件上设置一些状态,例如

class App extends Component {
  state = {
      aProperty: 'first'
  }

  myClickFunc = () => {
      console.log('clickity clickcty');
      this.setState({
          aProperty: 'second'
      });
  }
  render() {
    return (
      <div className="App">
        <div className="App-header">
          <img src={logo} className="App-logo" alt="logo" />
          <h2>Welcome to React</h2>
        </div>
        <p className="App-intro" onClick={this.myClickFunc}>
          To get started, edit <code>src/App.js</code> and save to reload.
        </p>
      </div>
    );
  }
}

您现在可以对组件的状态进行断言,即

describe('my sweet test', () => {
 it('clicks it', () => {
    const app = shallow(<App />)
    const p = app.find('.App-intro')
    p.simulate('click')
    expect(app.state('aProperty')).toEqual('second');
 })
})
一个类是一个对象。spyOnClassName.prototype
2021-06-01 11:26:21
一个类不是一个对象。在经典的面向对象中,它是一个对象的蓝图,在 JavaScript 中它是一个函数。 typeof (class A {}) === "function" 当我们实例化一个类时,我们会根据类的原型创建一个对象。类的原型是一个对象,如果我们愿意,我们可以监视方法。最终,根据我在您的回答下的评论,我们想要测试点击处理程序的效果,而不仅仅是它已被调用。
2021-06-20 11:26:21