web-dev-qa-db-fra.com

Comment tester React PropTypes via Jest?

J'écris des tests Jest pour mon code React et j'espère utiliser/tester les contrôles PropType. Je suis assez nouveau dans l'univers Javascript. J'utilise npm pour installer react-0.11.2 et avoir un simple:

var React = require('react/addons');

Dans mes tests. Mon test ressemble assez à l'exemple du didacticiel jest/react avec du code comme:

var eventCell = TestUtils.renderIntoDocument(
  <EventCell
    slot={slot}
    weekId={weekId}
    day={day}
    eventTypes={eventTypes}
    />
);

var time = TestUtils.findRenderedDOMComponentWithClass(eventCell, 'time');
expect(time.getDOMNode().textContent).toEqual('19:00 ');

Cependant, il semble que les vérifications PropType dans le composant EventCell ne soient pas déclenchées. Je comprends que les vérifications ne sont exécutées qu'en mode Développement, mais j'ai également pensé que le fait d'obtenir react via npm vous donnait la version de développement. Les vérifications se déclenchent dans mon navigateur lorsque je crée le composant avec watchify.

Qu'est-ce que je rate?

27
MichaelJones

Le problème sous-jacent est Comment tester console.log?

La réponse courte est que vous devez remplacer le console.{method} pendant la durée du test. L'approche courante consiste à utiliser espions . Dans ce cas particulier, vous pouvez utiliser stubs pour empêcher la sortie.

Voici un exemple d'implémentation utilisant Sinon.js (Sinon.js fournit des espions, des talons et des simulateurs autonomes):

import {
    expect
} from 'chai';
import DateName from './../../src/app/components/DateName';
import createComponent from './create-component';
import sinon from 'sinon';

describe('DateName', () => {
    it('throws an error if date input does not represent 12:00:00 AM UTC', () => {
        let stub;

        stub = sinon.stub(console, 'error');

        createComponent(DateName, {date: 1470009600000});

        expect(stub.calledOnce).to.equal(true);
        expect(stub.calledWithExactly('Warning: Failed propType: Date unix timestamp must represent 00:00:00 (HH:mm:ss) time.')).to.equal(true);

        console.error.restore();
    });
});

Dans cet exemple, le composant DataName générera une erreur lorsqu'il sera initialisé avec une valeur d'horodatage qui ne représente pas une date précise (12:00:00 AM).

Je stoppe le console.error méthode (C'est ce que le module Facebook warning utilise en interne pour générer l'erreur). Je m'assure que le stub a été appelé une fois et avec exactement un paramètre représentant l'erreur.

33
Gajus

Intro

La réponse de @Gajus m'a certainement aidé (donc, merci Gajus ). Cependant, j'ai pensé que je fournirais une réponse qui:

  • Utilise un React plus à jour (v15.4.1)
  • Utilise Jest (fourni avec React)
  • Permet de tester plusieurs valeurs d'accessoires pour un seul accessoire
  • Est plus générique

Résumé

Comme l'approche suggérée ici par Gajus et ailleurs par d'autres, l'approche de base que je suggère est également de déterminer si oui ou non console.error est utilisé par React en réponse à une valeur d'accessoire de test inacceptable . Plus précisément, cette approche consiste à effectuer les opérations suivantes pour chaque valeur d'accessoire de test:

  • moquerie et effacement console.error (pour garantir les appels antérieurs à console.error n'interfèrent pas),
  • créer le composant à l'aide de la valeur de test prop considérée, et
  • confirmant si oui ou non console.error a été renvoyé comme prévu.

La fonction testPropTypes

Le code suivant peut être placé dans le test ou en tant que module/fichier séparé/importé/requis:

const testPropTypes = (component, propName, arraysOfTestValues, otherProps) => {
    console.error = jest.fn();
    const _test = (testValues, expectError) => {
        for (let propValue of testValues) {
            console.error.mockClear();
            React.createElement(component, {...otherProps, [propName]: propValue});
            expect(console.error).toHaveBeenCalledTimes(expectError ? 1 : 0);
        }
    };
    _test(arraysOfTestValues[0], false);
    _test(arraysOfTestValues[1], true);
};

Appel de la fonction

Tout test examinant propTypes peut appeler testPropTypes en utilisant trois ou quatre paramètres :

  • component, le composant React qui est modifié par l'hélice;
  • propName, la chaîne nom de l'hélice sous test;
  • arraysOfTestValues, un tableau de tableaux de toutes les valeurs de test souhaitées de l'hélice à tester:
    • le premier sous-tableau contient toutes les valeurs de test acceptables , tandis que
    • le deuxième sous-tableau contient toutes les valeurs d'accessoires de test inacceptables ; et
  • facultativement, otherProps, un objet contenant des paires nom/valeur d'accessoire pour tout autre accessoire requis de ce composant.

    L'objet otherProps est nécessaire pour garantir que React ne fait pas d'appels non pertinents à console.error parce que d'autres accessoires requis manquent par inadvertance. Incluez simplement une seule valeur acceptable pour tous les accessoires requis, par exemple {requiredPropName1: anyAcceptableValue, requiredPropName2: anyAcceptableValue}.

Logique de fonction

La fonction effectue les opérations suivantes:

  • Il met en place une maquette de console.error qui est ce que React utilise pour signaler les accessoires de type incorrect.

  • Pour chaque sous-tableau de valeurs d'accessoire de test à condition qu'il boucle à travers chaque valeur d'accessoire de test dans chaque sous-réseau pour tester le type d'hélice:

    • Le premier des deux sous-tableaux doit être une liste de valeurs de test acceptables .
    • La seconde doit avoir des valeurs d'accessoires de test inacceptables .
  • Dans la boucle pour chaque valeur de test individuelle, le console.error mock est d'abord effacé afin que tout message d'erreur détecté puisse être supposé provenir de ce test.

  • Une instance du composant est ensuite créée à l'aide de la valeur d'accessoire de test ainsi que de tout autre accessoire requis nécessaire qui n'est pas actuellement testé.

  • Enfin, une vérification est effectuée pour voir si un avertissement a été déclenché , ce qui devrait se produire si votre test a essayé de créer un composant en utilisant un accessoire inapproprié ou manquant .

Test des accessoires facultatifs par rapport aux accessoires requis

Notez que l'attribution de null (ou undefined) à une valeur d'accessoire est, du point de vue de React, essentiellement la même chose que de ne fournir aucune valeur pour cet accessoire. Par définition, cela est acceptable pour un accessoire optionnel mais inacceptable pour un accessoire requis. Ainsi, en plaçant null dans le tableau de valeurs acceptables ou inacceptables, vous testez si cet accessoire est facultatif ou requis respectivement .

Exemple de code

MyComponent.js (juste le propTypes):

MyComponent.propTypes = {
    myProp1: React.PropTypes.number,      // optional number
    myProp2: React.PropTypes.oneOfType([  // required number or array of numbers
        React.PropTypes.number,
        React.PropTypes.arrayOf(React.PropTypes.number)
    ]).isRequired

MyComponent.test.js:

describe('MyComponent', () => {

    it('should accept an optional number for myProp1', () => {
        const testValues = [
            [0, null],   // acceptable values; note: null is acceptable
            ['', []] // unacceptable values
        ];
        testPropTypes(MyComponent, 'myProp1', testValues, {myProp2: 123});
    });

    it('should require a number or an array of numbers for myProp2', () => {
        const testValues = [
            [0, [0]], // acceptable values
            ['', null] // unacceptable values; note: null is unacceptable
        ];
        testPropTypes(MyComponent, 'myProp2', testValues);
    });
});

Limitation de cette approche (IMPORTANT)

Il existe actuellement des limitations importantes sur la façon dont vous pouvez utiliser cette approche qui, si elle est dépassée, pourrait être la source de quelques bogues de test difficiles à tracer. Les raisons et les implications de ces limitations sont expliquées dans cet autre SO question/réponse . En résumé, pour les types d'accessoires simples, comme pour myProp1, vous pouvez tester autant de valeurs d'accessoires non -null de test que vous le souhaitez tant qu'elles sont toutes de types de données différents. Pour certains types d'accessoires complexes, comme pour myProp2, vous pouvez uniquement tester une valeur unique non -null prop inacceptable de tout type. Voir cette autre question/réponse pour une discussion plus approfondie.

7
Andrew Willems

Se moquer console.error ne convient pas aux tests unitaires! @AndrewWillems lié à n autre SO question dans un commentaire ci-dessus qui décrit les problèmes avec cette approche.

Consultez ce problème sur facebook/prop-types pour une discussion sur la capacité de cette bibliothèque à lancer au lieu de consigner les erreurs propType (au moment de la rédaction, ce n'est pas pris en charge).

J'ai publié une bibliothèque d'aide pour fournir ce comportement dans l'intervalle, check-prop-types . Vous pouvez l'utiliser comme ceci:

import PropTypes from 'prop-types';
import checkPropTypes from 'check-prop-types';

const HelloComponent = ({ name }) => (
  <h1>Hi, {name}</h1>
);

HelloComponent.propTypes = {
  name: PropTypes.string.isRequired,
};

let result = checkPropTypes(HelloComponent.propTypes, { name: 'Julia' }, 'prop', HelloComponent.name);
assert(`result` === null);

result = checkPropTypes(HelloComponent.propTypes, { name: 123 }, 'prop', HelloComponent.name);
assert(`result` === 'Failed prop type: Invalid prop `name` of type `number` supplied to `HelloComponent`, expected `string`.');
5
Phil

Un nouveau package jest-prop-type-error est simple à ajouter et échoue sur les erreurs PropType:

Installer via:

yarn add -D jest-prop-type-error

Ajoutez ensuite ce qui suit à votre package.json's setupFiles dans la section jest:

"setupFiles": [
  "jest-prop-type-error"
]
3
YPCrumble

Étant donné que ReactJS n'enverra que des avertissements à la console mais ne lancera pas réellement d'erreur, je teste les valeurs des accessoires de cette manière:

var myTestElement = TestUtils.renderIntoDocument(
<MyTestElement height={100} /> );

it("check MyTestElement props", function() {

   expect( typeof myTestElement.props.height ).toEqual ( 'number' );

});
1
Guy Laor