Функция Jest spyOn называется

Я пытаюсь написать простой тест для простого компонента React, и я хочу использовать Jest, чтобы подтвердить, что функция была вызвана, когда я имитирую клик с помощью фермента. Согласно документам Jest, я должен использовать spyOn для этого: spyOn.

Однако, когда я пытаюсь это сделать, я продолжаю получать TypeError: Cannot read property '_isMockFunction' of undefined, который я подразумеваю, что мой шпион 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()
 })
})

У кого-нибудь есть понимание того, что я делаю неправильно?

Ответ 1

Эй, приятель, я знаю, что я немного опоздал, но ты почти закончил без каких-либо изменений, кроме того, как ты spyOn. Когда вы используете шпиона, вы должны наблюдать прототип компонента.

Порядок прикрепления шпиона в прототипе класса и рендеринг (мелкая визуализация) вашего экземпляра важны.

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

Бит App.prototype в первой строке - это то, что вам нужно, чтобы все работало. Javascript class не имеет никаких своих методов, пока вы не создадите экземпляр с помощью new MyClass(), или вы не погрузитесь в MyClass.prototype. Для вашего конкретного вопроса вам просто нужно подсмотреть метод App.prototype myClickFn.

Надеюсь, что это поможет кому-то еще, кто в противном случае попытался бы передать опору в свое приложение, чтобы пройти тест.

EDIT: Если вы хотите проверить побочные эффекты вашего 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");
*/

Ответ 2

В вашем тестовом коде вы пытаетесь передать App функции spyOn, но spyOn будет работать только с объектами, а не с классами. Обычно вам нужно использовать один из двух способов:

1) Если обработчик кликов вызывает функцию, переданную в качестве опоры, например

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>
    );
  }
}

Теперь вы можете передать функцию шпиона в качестве опоры для компонента и утверждать, что она вызывается:

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');
 })
})

Ответ 3

Ты почти там. Хотя я согласен с ответом @Alex Young на использование реквизита для этого, вам просто нужна ссылка на 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()
 })
})

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