web-dev-qa-db-fra.com

Testez une fonction React Component avec Jest

Original

Tout d'abord, je suis l'architecture Flux .

J'ai un indicateur qui affiche un nombre de secondes, ex: 30 secondes. Chaque seconde, il affiche 1 seconde de moins, donc 29, 28, 27 jusqu'à 0. Lorsque arrive à 0, j'efface l'intervalle pour qu'il cesse de se répéter. De plus, je déclenche une action. Lorsque cette action est envoyée, ma boutique m'en informe. Donc, lorsque cela se produit, je réinitialise l'intervalle à 30 secondes et ainsi de suite. Le composant ressemble à:

var Indicator = React.createClass({

  mixins: [SetIntervalMixin],

  getInitialState: function(){
    return{
      elapsed: this.props.rate
    };
  },

  getDefaultProps: function() {
    return {
      rate: 30
    };
  },

  propTypes: {
    rate: React.PropTypes.number.isRequired
  },

  componentDidMount: function() {
    MyStore.addChangeListener(this._onChange);
  },

  componentWillUnmount: function() {
    MyStore.removeChangeListener(this._onChange);
  },

  refresh: function(){
    this.setState({elapsed: this.state.elapsed-1})

    if(this.state.elapsed == 0){
      this.clearInterval();
      TriggerAnAction();
    }
  },

  render: function() {
    return (
      <p>{this.state.elapsed}s</p>
    );
  },

  /**
   * Event handler for 'change' events coming from MyStore
   */
  _onChange: function() {
    this.setState({elapsed: this.props.rate}
    this.setInterval(this.refresh, 1000);
  }

});

module.exports = Indicator;

Le composant fonctionne comme prévu. Maintenant, je veux le tester avec Jest. Je sais que je peux utiliser renderIntoDocument, alors je peux définir TimeTime de 30 secondes et vérifier si mon component.state.elapsed est égal à 0 (par exemple).

Mais ce que je veux tester ici, ce sont des choses différentes. Je veux tester si la fonction de rafraîchissement est appelée . De plus, je voudrais tester que lorsque mon état écoulé est 0, il déclenche mon TriggerAnAction () . Ok, pour la première chose que j'ai essayé de faire:

jest.dontMock('../Indicator');

describe('Indicator', function() {
  it('waits 1 second foreach tick', function() {

    var React = require('react/addons');
    var Indicator = require('../Indicator.js');
    var TestUtils = React.addons.TestUtils;

    var Indicator = TestUtils.renderIntoDocument(
      <Indicator />
    );

    expect(Indicator.refresh).toBeCalled();

  });
});

Mais je reçois l'erreur suivante lors de l'écriture du test npm:

Throws: Error: toBeCalled() should be used on a mock function

J'ai vu de ReactTestUtils une fonction mockComponent mais étant donné son explication, je ne sais pas si c'est ce dont j'ai besoin.

Ok, sur ce point, je suis coincé. Quelqu'un peut-il m'éclairer sur la façon de tester ces deux choses que j'ai mentionnées ci-dessus?


Mise à jour 1, basée sur la réponse d'Ian

C'est le test que j'essaie de lancer (voir les commentaires sur certaines lignes):

jest.dontMock('../Indicator');

describe('Indicator', function() {
  it('waits 1 second foreach tick', function() {

    var React = require('react/addons');
    var Indicator = require('../Indicator.js');
    var TestUtils = React.addons.TestUtils;

    var refresh = jest.genMockFunction();
    Indicator.refresh = refresh;

    var onChange = jest.genMockFunction();
    Indicator._onChange = onChange;

    onChange(); //Is that the way to call it?

    expect(refresh).toBeCalled(); //Fails
    expect(setInterval.mock.calls.length).toBe(1); //Fails

    // I am trying to execute the 1 second timer till finishes (would be 60 seconds)
    jest.runAllTimers();

    expect(Indicator.state.elapsed).toBe(0); //Fails (I know is wrong but this is the idea)
    expect(clearInterval.mock.calls.length).toBe(1); //Fails (should call this function when time elapsed is 0)

  });
});

Je ne comprends toujours pas quelque chose ...

32
Ferran Negre

Il semble que vous soyez sur la bonne voie. Juste pour nous assurer que tout le monde est sur la même page pour cette réponse, éliminons la terminologie.

Mock : Fonction dont le comportement est contrôlé par le test unitaire. Vous échangez généralement des fonctions réelles sur un objet avec une fonction de simulation pour vous assurer que la fonction de simulation est correctement appelée. Jest fournit automatiquement des simulations pour chaque fonction d'un module, sauf si vous appelez jest.dontMock sur le nom de ce module.

Classe de composants : c'est la chose retournée par React.createClass. Vous l'utilisez pour créer des instances de composants (c'est plus compliqué que cela, mais cela suffit pour nos besoins).

Instance de composant : instance réelle rendue d'une classe de composants. Voici ce que vous obtiendrez après avoir appelé TestUtils.renderIntoDocument ou de nombreuses autres fonctions TestUtils.


Dans votre exemple mis à jour de votre question, vous générez des maquettes et les attachez au composant class au lieu d'un instance du composant. En outre, vous souhaitez uniquement simuler les fonctions que vous souhaitez surveiller ou modifier; par exemple, vous vous moquez de _onChange, mais vous ne voulez pas vraiment, parce que vous voulez qu'il se comporte normalement - c'est seulement refresh que vous voulez vous moquer.

Voici un ensemble de tests proposé que j'ai écrit pour ce composant; les commentaires sont en ligne, alors postez un commentaire si vous avez des questions. La source complète et fonctionnelle de cet exemple et de cette suite de tests se trouve à https://github.com/BinaryMuse/so-jest-react-mock-example/tree/master ; vous devriez pouvoir le cloner et l'exécuter sans problème. Notez que j'ai dû faire quelques suppositions et modifications mineures sur le composant car tous les modules référencés n'étaient pas dans votre question d'origine.

/** @jsx React.DOM */

jest.dontMock('../indicator');
// any other modules `../indicator` uses that shouldn't
// be mocked should also be passed to `jest.dontMock`

var React, IndicatorComponent, Indicator, TestUtils;

describe('Indicator', function() {
  beforeEach(function() {
    React = require('react/addons');
    TestUtils = React.addons.TestUtils;
    // Notice this is the Indicator *class*...
    IndicatorComponent = require('../indicator.js');
    // ...and this is an Indicator *instance* (rendered into the DOM).
    Indicator = TestUtils.renderIntoDocument(<IndicatorComponent />);
    // Jest will mock the functions on this module automatically for us.
    TriggerAnAction = require('../action');
  });

  it('waits 1 second foreach tick', function() {
    // Replace the `refresh` method on our component instance
    // with a mock that we can use to make sure it was called.
    // The mock function will not actually do anything by default.
    Indicator.refresh = jest.genMockFunction();

    // Manually call the real `_onChange`, which is supposed to set some
    // state and start the interval for `refresh` on a 1000ms interval.
    Indicator._onChange();
    expect(Indicator.state.elapsed).toBe(30);
    expect(setInterval.mock.calls.length).toBe(1);
    expect(setInterval.mock.calls[0][1]).toBe(1000);

    // Now we make sure `refresh` hasn't been called yet.
    expect(Indicator.refresh).not.toBeCalled();
    // However, we do expect it to be called on the next interval tick.
    jest.runOnlyPendingTimers();
    expect(Indicator.refresh).toBeCalled();
  });

  it('decrements elapsed by one each time refresh is called', function() {
    // We've already determined that `refresh` gets called correctly; now
    // let's make sure it does the right thing.
    Indicator._onChange();
    expect(Indicator.state.elapsed).toBe(30);
    Indicator.refresh();
    expect(Indicator.state.elapsed).toBe(29);
    Indicator.refresh();
    expect(Indicator.state.elapsed).toBe(28);
  });

  it('calls TriggerAnAction when elapsed reaches zero', function() {
    Indicator.setState({elapsed: 1});
    Indicator.refresh();
    // We can use `toBeCalled` here because Jest automatically mocks any
    // modules you don't call `dontMock` on.
    expect(TriggerAnAction).toBeCalled();
  });
});
46
Michelle Tilley

Je pense que je comprends ce que vous demandez, au moins en partie!

En commençant par l'erreur, la raison pour laquelle vous voyez cela est parce que vous avez demandé à Jest de ne pas se moquer du module indicateur de sorte que tous les éléments internes soient tels que vous les avez écrits. Si vous voulez tester cette fonction particulière, je vous suggère de créer une fonction fictive et de l'utiliser à la place ...

var React = require('react/addons');
var Indicator = require('../Indicator.js');
var TestUtils = React.addons.TestUtils;

var refresh = jest.genMockFunction();
Indicator.refresh = refresh; // this gives you a mock function to query

La prochaine chose à noter est que vous êtes en train de réaffecter la variable indicateur dans votre exemple de code, donc pour un bon comportement, je renommerais la deuxième variable (comme ci-dessous)

var indicatorComp = TestUtils.renderIntoDocument(<Indicator />);

Enfin, si vous souhaitez tester quelque chose qui change au fil du temps, utilisez les fonctionnalités TestUtils autour de la manipulation de la minuterie ( http://facebook.github.io/jest/docs/timer-mocks.html ). Dans votre cas, je pense que vous pouvez faire:

jest.runAllTimers();

expect(refresh).toBeCalled();

Alternativement, et peut-être un peu moins difficile, vous pouvez vous fier aux implémentations fictives de setTimeout et setInterval pour raisonner sur votre composant:

expect(setInterval.mock.calls.length).toBe(1);
expect(setInterval.mock.calls[0][1]).toBe(1000);

Une autre chose, pour que l'une des modifications ci-dessus fonctionne, je pense que vous devrez déclencher manuellement la méthode onChange car votre composant fonctionnera initialement avec une version simulée de votre magasin afin qu'aucun événement de changement ne se produise. Vous devrez également vous assurer que vous avez défini plaisanterie pour ignorer les modules de réaction, sinon ils seront automatiquement moqués également.

Test complet proposé

jest.dontMock('../Indicator');

describe('Indicator', function() {
  it('waits 1 second for each tick', function() {
    var React = require('react/addons');
    var TestUtils = React.addons.TestUtils;

    var Indicator = require('../Indicator.js');
    var refresh = jest.genMockFunction();
    Indicator.refresh = refresh;

    // trigger the store change event somehow

    expect(setInterval.mock.calls.length).toBe(1);
    expect(setInterval.mock.calls[0][1]).toBe(1000);

  });

});
6
Ian Thomas