79 votes

La fonction Jest spyOn est appelée

J'essaie d'écrire un test simple pour un composant React simple, et je veux utiliser Jest pour confirmer qu'une fonction a été appelée lorsque je simule un clic avec enzyme. Selon la documentation de Jest, je devrais pouvoir utiliser spyOn pour le faire : espionner .

Cependant, lorsque j'essaie de le faire, j'obtiens toujours TypeError: Cannot read property '_isMockFunction' of undefined ce qui signifie, selon moi, que mon espion est indéfini. Mon code ressemble à ceci :

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;

et dans mon fichier de test :

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

Quelqu'un a-t-il une idée de ce que je fais mal ?

96voto

taystack Points 1030

Vous avez presque terminé sans aucun changement à part la façon dont vous spyOn . Lorsque vous utilisez l'espion, vous avez deux options : spyOn el App.prototype ou composant component.instance() .


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

L'ordre d'attachement de l'espion sur le prototype de la classe et de rendu (shallow rendering) de votre instance est important.

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

El App.prototype sur la première ligne, il y a ce dont vous avez besoin pour que les choses fonctionnent. Un JavaScript class ne dispose d'aucune de ses méthodes tant que vous ne l'instanciez pas avec la commande new MyClass() ou vous plongez dans le MyClass.prototype . Pour votre question particulière, vous aviez juste besoin d'espionner le App.prototype méthode myClickFn .


jest.spyOn(composant.instance(), "method")

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

Cette méthode nécessite un shallow/render/mount instance d'un React.Component pour être disponible. Essentiellement spyOn est juste à la recherche de quelque chose à détourner et à mettre dans un jest.fn() . C'est possible :

Une simple object :

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

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.

Ou un React.Component instance :

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

Ou un 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.

J'ai utilisé et vu les deux méthodes. Lorsque j'ai un beforeEach() o beforeAll() bloc, je pourrais opter pour la première approche. Si j'ai juste besoin d'un espion rapide, j'utiliserai la seconde. Il faut juste faire attention à l'ordre de fixation de l'espion.


EDIT : Si vous voulez vérifier les effets secondaires de votre myClickFn vous pouvez simplement l'invoquer dans un test séparé.

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");
*/

EDIT : Voici un exemple d'utilisation d'un composant fonctionnel. Gardez à l'esprit que toutes les méthodes scopées dans votre composant fonctionnel ne sont pas disponibles pour l'espionnage. Vous espionnez les fonctions passées dans votre composant fonctionnel et testez l'invocation de celles-ci. Cet exemple explore l'utilisation de jest.fn() à l'opposé de jest.spyOn qui partagent tous deux l'API de la fonction de simulation. Bien qu'elle ne réponde pas à la question initiale, elle donne un aperçu d'autres techniques qui pourraient convenir à des cas indirectement liés à la question.

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*/);

1 votes

Je suis tombé sur ce programme, et il ne semble pas coopérer. J'essaie de faire passer un petit test et ça ne marche pas.

1 votes

@Byrd Je ne suis pas sûr de ce que vous voulez dire. C'est juste que ça ne marche pas, spyOn ne fonctionne pas, ou quelque chose de différent ? Est-ce que votre package.json correctement configuré pour la façon dont vous configurez jest ? Tant de questions sur vos déclarations.

0 votes

Si la question était "Comment puis-je utiliser A pour faire B", mais que vous saviez que l'utilisation de C était un meilleur moyen d'atteindre A, alors il est probablement approprié de répondre C. Je n'ai aucun problème avec spyOn, mais l'utiliser pour espionner les gestionnaires de clics dans les composants React est une approche absurde des tests dans 99% des situations.

31voto

CharlieBrown Points 356

Vous y êtes presque. Bien que je sois d'accord avec la réponse de @Alex Young concernant l'utilisation des props pour cela, vous avez simplement besoin d'une référence à l'élément instance avant d'essayer d'espionner la méthode.

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

4 votes

Avant que le clic de simulation ne soit appelé, appelez forceUpdate pour attacher la fonction espionne à l'instance : instance.forceUpdate()

0 votes

Etrange Je n'ai pas réussi à faire fonctionner ce qui précède pour un test similaire, mais le fait de changer la méthode de rendu de l'application de 'shallow' à 'mount' a réglé le problème. Avez-vous une idée de la raison pour laquelle cela aurait pu être la solution ou pourquoi 'mount' n'est pas également requis pour ce test ?

1 votes

@youngrrrr peut-être que votre fonction s'appuie sur le DOM, que shallow ne produit pas, alors que mount est un rendu DOM complet

14voto

Alex Young Points 3203

Dans votre code de test, vous essayez de faire passer App à la fonction spyOn, mais spyOn ne fonctionnera qu'avec des objets, et non des classes. En général, vous devez utiliser l'une des deux approches suivantes :

1) Lorsque le gestionnaire de clics appelle une fonction transmise en tant que prop, par ex.

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

Vous pouvez maintenant passer une fonction espionne en tant que prop au composant, et affirmer qu'elle est appelée :

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) Lorsque le gestionnaire de clics définit un certain état sur le composant, par ex.

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

Vous pouvez maintenant faire des assertions sur l'état du composant, c'est à dire

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

4 votes

Une classe est un objet. spyOn travaille avec ClassName.prototype

2 votes

Une classe n'est pas un objet. En OO classique, c'est le plan d'un objet, en JavaScript, c'est une fonction. typeof (class A {}) === "function" Lorsque nous instancions une classe, nous créons un objet, basé sur le prototype de la classe. Le prototype d'une classe est un objet, et nous pouvons espionner les méthodes si nous le voulons. En fin de compte, comme je l'ai dit dans ma réponse, nous voulons tester l'effet d'un gestionnaire de clic, et pas seulement le fait qu'il ait été appelé.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X