Comment tester des composants de classe dans React

9

J'essaie des tests unitaires, j'ai créé un sandbox avec un faux exemple https://codesandbox.io/s/wizardly-hooks-32w6l (en réalité j'ai un formulaire)

class App extends React.Component {
  constructor(props) {
    super(props);
    this.state = { number: 0 };    
  }

  handleSubmit = (number1, number2) => {
    this.setState({ number: this.handleMultiply(number1, number2) })
  }

  handleMultiply = (number1, number2) => {
    return number1 * number2
  }

  render() {
    const { number } = this.state;

    return (
      <div className="App">
        <form onSubmit={e => this.handleSubmit(3, 7)}>       
          <input type="submit" name="Submit" value="Multiply" />
        </form>
        <Table number={number} />
      </div>
    );
  }
}

export default App;

Mon idée initiale était donc d'essayer de tester la fonction multiplier. Et a fait cela, ce qui ne fonctionne évidemment pas

import App from "../src/App";

test("Multiply", function() {
  const expected = 21;
  const result = App.handleMultiply(3, 7);
  expect(result).toBe(expected);
});

Je reçois

_App.default.handleMultiply n'est pas une fonction

Mon approche est-elle correcte? Si oui, comment puis-je tester les fonctions? Sinon, dois-je tester d'un point de vue utilisateur au lieu de pour les fonctions internes (c'est ce que j'ai lu)? Dois-je tester la sortie à l'écran (je ne pense pas que ce soit raisonnable)?

user3808307
la source
2
Vous approchez cela avec la mauvaise mentalité. Au lieu de cela, déclenchez la soumission du formulaire, puis vérifiez que l'état a bien été mis à jour, y compris la logique de multiplication.
Alexander Staroselsky
@AlexanderStaroselsky ok, merci, je vais essayer, et faire une question plus spécifique si je suis bloqué
user3808307
@AlexanderStaroselsky et si le formulaire dans un composant enfant et les gestionnaires de soumission dans le parent? Dois-je y faire des tests d'intégration?
user3808307
1
C'est peut-être une question d'opinion, mais je les testerais certainement séparément. Les tests pour l'enfant seraient que lors de la soumission, il déclenche la fonction transmise par le parent via des accessoires, puis également pour tester le rendu de l'état comme prévu. Pour le parent, je déclencherais l'événement et m'assurerais que l'état a été mis à jour correctement.
Alexander Staroselsky
@AlexanderStaroselsky Merci
user3808307

Réponses:

4

Vous pouvez utiliser la méthode instance () de enzymepour obtenir l'instance de React Component. Ensuite, appelez handleMultiplydirectement method et effectuez l'assertion correspondante. De plus, si la handleMultiplyméthode a un effet secondaire ou des calculs très compliqués, vous devez lui faire une simple valeur de retour simulée. Il fera un environnement de test isolé pour la handleSubmitméthode. Cela signifie que la handleSubmitméthode ne dépendra pas de la valeur de retour de l'implémentation réelle de la handleMultiplyméthode.

Par exemple

app.jsx:

import React from 'react';
import { Table } from './table';

class App extends React.Component {
  constructor(props) {
    super(props);
    this.state = { number: 0 };
  }

  handleSubmit = (number1, number2) => {
    this.setState({ number: this.handleMultiply(number1, number2) });
  };

  handleMultiply = (number1, number2) => {
    return number1 * number2;
  };

  render() {
    const { number } = this.state;

    return (
      <div className="App">
        <form onSubmit={(e) => this.handleSubmit(3, 7)}>
          <input type="submit" name="Submit" value="Multiply" />
        </form>
        <Table number={number} />
      </div>
    );
  }
}

export default App;

table.jsx:

import React from 'react';

export const Table = ({ number: num }) => {
  return <div>table: {num}</div>;
};

app.test.jsx:

import App from './app';
import { shallow } from 'enzyme';

describe('59796928', () => {
  let wrapper;
  beforeEach(() => {
    wrapper = shallow(<App></App>);
  });
  describe('#handleSubmit', () => {
    it('should pass', () => {
      expect(wrapper.exists()).toBeTruthy();
      wrapper.find('form').simulate('submit');
      expect(wrapper.state()).toEqual({ number: 21 });
    });
  });
  describe('#handleMultiply', () => {
    it('should pass', () => {
      const comp = wrapper.instance();
      const actual = comp.handleMultiply(2, 10);
      expect(actual).toBe(20);
    });
  });
});

Résultats des tests unitaires avec rapport de couverture:

 PASS  src/stackoverflow/59796928/app.test.jsx (11.688s)
  59796928
    #handleSubmit
       should pass (16ms)
    #handleMultiply
       should pass (9ms)

-----------|----------|----------|----------|----------|-------------------|
File       |  % Stmts | % Branch |  % Funcs |  % Lines | Uncovered Line #s |
-----------|----------|----------|----------|----------|-------------------|
All files  |    90.48 |      100 |    85.71 |    94.44 |                   |
 app.jsx   |      100 |      100 |      100 |      100 |                   |
 table.jsx |       50 |      100 |        0 |    66.67 |                 4 |
-----------|----------|----------|----------|----------|-------------------|
Test Suites: 1 passed, 1 total
Tests:       2 passed, 2 total
Snapshots:   0 total
Time:        13.936s

Code source: https://github.com/mrdulin/jest-codelab/tree/master/src/stackoverflow/59796928

slideshowp2
la source
Et si le formulaire était dans un composant enfant? Comment pourrais-je déclencher handleSubmit dans le test, autre qu'avec un formulaire à soumettre? Merci
user3808307