Pourquoi mon test de jest des propTypes React se casse-t-il lorsque j'utilise plusieurs valeurs de props inacceptables?

Dans une réponse à une autre DONC, la question sur la façon de tester propTypes à Réagir à l'aide de Plaisanterie, j'ai proposé une solution qui se moque console.error , quelque chose que les autres l'ont fait avant, mais j'ai pensé que peut être amélioré. Ma solution, une fonction, est ci-dessous. Le code d'exemple ci-dessous contient quelques propTypes arbitraires qui sont ensuite testées. Pour chaque essai, je m'attendais à ce que le premier sous-tableau de "valeurs acceptables" ne provoque pas la moquerie de console.error d'avoir été appelé par React, mais je m'attendais à ce que chaque valeur d'essai prop dans le second sous-tableau de "valeurs inacceptables" fasse que la simulation ait été appelée.

La testPropTypes Fonction

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

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
            ['', {}, [], Symbol(), true] // 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
            ['', {}, [], Symbol(), true, null] // unacceptable values
        ];
        testPropTypes(MyComponent, 'myProp2', testValues);
    });
});

Le code semble bien fonctionner dans de simples circonstances. Toutefois , le sous-tableau des valeurs inacceptables des prop de test pour le complexe propType , c.-à-d. pour myProp2 , produit quelques résultats amusants. La présence ou l'absence de null (qui vérifie si l'hélice est optionnelle ou requise) ne fait aucune différence, c'est-à-dire qu'elle se comporte correctement tout le temps. Toutefois, les autres valeurs du type prop d'essai semblent interagir entre elles dans une façon mystérieuse. Si une seule valeur non null inacceptable est utilisée, l'essai se déroule comme prévu. Toutefois, si plus d'une valeur non null inacceptable est utilisée, l'essai est interrompu, répondant comme si la seconde, la troisième, etc. les types inacceptables (encore une fois, autres que null ) sont acceptables.

que se passe-t-il et comment puis-je réparer cela?

1
demandé sur Community 2017-01-29 05:15:18

2 réponses

Courte Réponse

la réponse courte est que, pour au moins quelques complexes propTypes comme pour myProp2 , vous pouvez seulement inclure un seul non - null type d'hélice d'essai de n'importe quel type. C'est une limitation inhérente à cette approche, et vient d'une caractéristique de Réagir/Plaisanterie qui provoque plusieurs conditions d'erreur qui, autrement, auraient produit identique messages d'erreur apparaît qu'une seule fois. En fait, même pour simple propTypes comme pour myProp1 , une limite apparentée mais légèrement plus cachée existe aussi: vous pouvez inclure n'importe quel nombre de différentes valeurs inacceptables Non - null prop test, mais jamais plus d'un du même type . par exemple ['', {}, []] fonctionnera mais ['', 'x', {}, []] ne fonctionnera pas (notez qu'il y a deux chaînes).

cette limitation est importante car il ne semble pas y avoir actuellement une meilleure façon de tester propTypes dans React. Toutefois, les contraintes que cela impose à cette approche semblent gérables, permettant à propTypes d'être raisonnablement testé.

détails

il y a actuellement des limites importantes sur la façon dont vous pouvez utiliser cette approche (i.e. moquerie console.error ) qui, si elle est trop poussée, pourrait être la source de quelques bogues de test difficiles à tracer.

la source de ces limitations est un comportement de React (ou peut-être plaisanter pour réagir?) qui est intégré à son mécanisme de déclaration des erreurs. Elle ne semble actuellement produire que des messages multiples console.error , du moins pour les questions propTypes , lorsque ces messages sont distincts les uns des autres. On peut supposer que le raisonnement derrière ceci est que des messages identiques répétés ne fournissent aucune nouvelle information et donc un tel message ne devrait être montré que la première fois, ce qui semble raisonnable. L'implication de ceci, cependant, est que deux conditions d'erreur qui, lorsqu'ils se produisent séparément, produisent des messages d'erreur identiques ne donnent lieu à un seul message d'erreur que lorsqu'ils se produisent au cours du même essai. Tous les messages identiques subséquents sont supprimés. Cela a plusieurs autres implications pour les stratégies telles que celle-ci qui s'attendent nécessairement à ce que le résultat de console.error soit exactement corrélé avec les conditions de test-échec, comme suit.

actuellement, pour simple propTypes (comme celle pour myProp1 dans l'exemple code), des messages d'erreur distincts semblent apparaître pour toutes les valeurs prop inacceptables de différents types de données, par exemple

"Warning: Failed prop type: Invalid prop 'myProp1' of type 'string'
supplied to 'MyComponent', expected 'number'."

toutefois, le message d'erreur pour une deuxième valeur d'essai du même type semble avoir été supprimé. Ainsi, un tableau de valeurs inacceptables de tous les différents types de données, comme [123, [123], '123'] , fera que chacun produira un message d'erreur comme prévu, ce qui permettra au test de fonctionner. Cependant, un tableau des valeurs inacceptables dans lesquelles certains ont les types de données identiques, tels que [123, 456, [123], '123'] , ne fonctionnera pas , car seule la première valeur d'un type de données particulier produira le message d'erreur attendu, et donc les messages d'erreur présumée pour toutes les valeurs semblables ultérieures seront supprimés, ce qui va à l'encontre du test. Ainsi, pour le simple propTypes , vous pouvez utiliser autant de valeurs inacceptables que vous voulez tant qu'ils sont tous de données différentes types de . Cela ne semble pas être un gros problème, car vous ne devriez pas avoir besoin de tester plus d'une valeur d'un type de données particulier de toute façon, mais vous devez être conscient de cette mise en garde.

peut-être encore plus important, Cependant, certains complexes propTypes (comme celui pour myProp2 ) paradoxalement (bien que peut-être raisonnablement) entraîner des messages d'erreur encore plus simplifiés, par exemple

"Warning: Failed prop type: Invalid prop 'myProp2' supplied to 'MyComponent'."

Cela signifie, cependant, que seul le premier non- null inacceptable test prop value de tout type de données produit un message d'erreur, avec tous tests ultérieurs utilisant d'autres valeurs inacceptables ayant leurs messages d'erreur supprimés. Ainsi , avec un tel complexe propTypes , malheureusement vous pouvez inclure seulement une seule valeur prop inacceptable pour un accessoire particulier pour un composant particulier dans votre suite d'essai. Cela ne comprend pas un null ou undefined valeur pour un complexe requis propType , car ces valeurs produisent un message d'erreur distinct qui n'est donc pas supprimé. Notez, cependant, que vous pouvez toujours can tester autant de valeurs acceptables que vous le souhaitez, par exemple en vous permettant de tester plusieurs valeurs props acceptables possibles pour myProp2 dans le code exemple. Ce qui est encore plus déconcertant, c'est que cette limite ne s'applique pas seulement à un test , mais aussi entre les tests . Ainsi, toute épreuve la suite ne peut contenir qu'un seul essai de type d'hélice inacceptable pour une hélice particulière pour un composant particulier. Cela semble comme une limitation importante, et il est absolument nécessaire de garder à l'esprit. Toutefois, il se peut qu'il ne s'agisse pas d'une limite, car on ne s'attendrait généralement pas à tester des valeurs inacceptables pour une hélice particulière pour un composant particulier plusieurs fois de toute façon. Ainsi, cette approche, c'est-à-dire se moquant de console.error , semble toujours raisonnable, et cela semble toujours la seule vraie façon de tester propTypes de toute façon.

noter que cette limitation pourrait changer dans l'avenir, chaque fois que réagir et/ou plaisanter change la façon dont il utilise console.error en réponse à des valeurs props inappropriées.

Notez aussi que je n'ai pas cherché à savoir exactement comment React / Jest effectue le rapport d'erreur pour les autres complexes propTypes , par exemple React.PropTypes.arrayOf , ... .objectOf , ... .shape , etc. Probablement, pour tester ces robustesse vous faudrait d'abord étudier ce genre de messages d'erreur sont produits pour des valeurs inappropriées ainsi que si ces messages sont supprimés.

4
répondu Andrew Willems 2017-01-29 02:15:18

voici un peu de détails sur le pourquoi , pour développer la description détaillée D'Andrew Willems des conditions à l'origine du problème.

notez que mon explication ici s'applique à la bibliothèque prop-types que Facebook a récemment extraite de React, en particulier lors de la vérification des accessoires avec la fonction checkPropTypes qu'elle exporte.

le module checkPropTypes.js conserve l'état mutable dans la forme d'un objet appelé loggedTypeFailures sur ligne 16 :

 var loggedTypeFailures = {};

Lignes de 47-50 de montrer comment il est utilisé et expliquer la pensée:

      if (error instanceof Error && !(error.message in loggedTypeFailures)) {
        // Only monitor this failure once because there tends to be a lot of the
        // same error.
        loggedTypeFailures[error.message] = true;

Lorsqu'une vérification de propType échoue, le message est positionné comme une clé sur loggedTypeFailures avant la journalisation de l'erreur, pour suivre qu'il a déjà été journalisé. La prochaine fois qu'un propType échoue avec le même message, la vérification error.message in loggedTypeFailures réussit et la journalisation est sauter.

étant donné que loggedTypeFailures est privé au module, il n'y a aucun moyen d'y accéder ou de le réinitialiser.


Si vous avez besoin de détecter propType échecs dans une façon plus robuste (eg., pour les tests unitaires), j'ai publié une petite bibliothèque qui aide: check-prop-types . Il vérifie props de la même manière que checkPropTypes.js , mais renvoie l'erreur au lieu de l'enregistrer.

1
répondu Phil 2017-07-06 20:35:56