Redux: Test des composants connectés qui ont des composants connectés imbriqués dans

Créé le 10 nov. 2016  ·  21Commentaires  ·  Source: reduxjs/redux

Salut, c'est plus une question qu'un problème.

J'ai récemment commencé à utiliser React et Redux pour un projet et j'ai quelques problèmes avec les tests, mais la documentation ne semble pas apporter une solution concluante au problème.

À l'heure actuelle, j'ai un composant connecté pour lequel j'aimerais écrire des tests unitaires, mais il y a un composant connecté imbriqué dans le premier composant et je semble finir par être bloqué par les accessoires ou l'état (ou dans la plupart des cas les deux) de le composant imbriqué n'est pas défini.

Je me demandais s'il existe un moyen de remplacer un composant afin que je puisse me concentrer uniquement sur le composant que j'essaie de tester?

Merci d'avance

question

Commentaire le plus utile

Le problème soulevé ici est lorsque le composant testé rend ensuite d'autres composants connectés, soit en tant qu'enfants directs, soit quelque part dans la hiérarchie. Si vous effectuez un rendu complet via mount() , alors les composants connectés descendants essaieront absolument d'accéder à this.context.store , et ne le trouveront pas. Ainsi, les principales options sont soit de s'assurer que les descendants ne sont pas rendus en effectuant un test superficiel, soit de rendre le magasin disponible en contexte.

Tous les 21 commentaires

Deux choses:

1) c'est l'utilisation et devrait probablement aller au débordement de pile.

2) une partie de la commodité d'utiliser le composant d'ordre supérieur connect
est que vous pouvez simplement écrire un test unitaire pour le composant unconnected et
faites confiance à redux pour gérer la tuyauterie du magasin comme il se doit

Si ce que je veux dire par là n'est pas clair, je peux poster un exemple.

Quelques réflexions:

  • Une façon de se concentrer sur un seul composant est d'utiliser le rendu "superficiel", qui ne rend pas réellement les enfants
  • Vous pouvez également rendre <Provider store={testStore}><ConnectedComponent /></Provider> dans vos tests

Pour info, j'ai des liens vers un certain nombre d'articles sur les tests React / Redux ici, que vous pouvez trouver utiles: https://github.com/markerikson/react-redux-links/blob/master/react-redux-testing. md .

Et oui, en tant que question d'utilisation, c'est vraiment mieux posé ailleurs.

Merci @markerikson J'essaie actuellement la dernière méthode que vous avez mentionnée, mais je

Je pense toujours que si vous voulez tester ce composant une fois de manière isolée, vous feriez mieux de faire quelque chose comme

// Component.js
import React from 'react'

export const Component = ({ a, b, c }) => ( 
  // ...
)

// ...

export default connect(mapStateToProps, mapDispatchToProps)(Component)

alors vous pouvez simplement introduire le composant avant qu'il ne soit connecté dans votre test comme

// Component.spec.js
import { Component } from './Component.js'
import { mount } from 'enzyme'

it('should mount without exploding', () => {
  mount(<Component a={1} b={2} c={3} />)
})

Si ce que vous voulez vraiment tester, c'est que les modifications apportées à votre boutique se propagent correctement, il est logique de rendre le Provider et c'est différent, bien que je ne sois pas sûr que vous en tiriez une grande valeur.

Le problème soulevé ici est lorsque le composant testé rend ensuite d'autres composants connectés, soit en tant qu'enfants directs, soit quelque part dans la hiérarchie. Si vous effectuez un rendu complet via mount() , alors les composants connectés descendants essaieront absolument d'accéder à this.context.store , et ne le trouveront pas. Ainsi, les principales options sont soit de s'assurer que les descendants ne sont pas rendus en effectuant un test superficiel, soit de rendre le magasin disponible en contexte.

@markerikson Ah ok, je viens de relire je pensais que l'OP essayait de tester le composant interne plutôt que l'extérieur. 👍

Merci pour votre aide jusqu'à présent les gars,

Le principal problème que je rencontre pour le moment est que je n'arrive pas à transmettre l'état au composant connecté qui est imbriqué à l'intérieur, donc la vue n'est pas rendue correctement.

L'état moqueur en général est quelque chose avec lequel je me bats un peu en fait et je n'ai pas été en mesure de trouver quoi que ce soit de concluant sur le sujet, donc ce serait formidable si quelqu'un pouvait me donner une idée de la façon dont je passerais un état simulé à un Connecté imbriqué ou composant d'ordre supérieur?

TIA

@StlthyLee : qu'entendez-vous par "passer l'état"? Si vous affichez <Provider store={store}><ComponentWithConnectedDescendants /></Provider> dans votre test, cela devrait gérer les choses.

C'est le problème, faire cela ne le gère pas correctement, le composant imbriqué dans nécessite un paramètre spécifique de l'état de l'application qu'il n'obtient actuellement pas pour une raison ou une autre.

@StlthyLee devrait probablement mettre un exemple / gist / codepen / repo-link ou quelque chose de ce genre, cela prendra moins de temps pour repérer le problème car il semble y avoir des conflits de terminologie ici.

@Koleok j'espère que cela aidera à clarifier

https://gist.github.com/StlthyLee/02e116d945867a77c382fa0105af1bf3

si ce n'est pas le cas, demandez, car c'est quelque chose que je souhaite approfondir, après avoir passé les 9 à 10 dernières années à travailler dans le monde des rails et avoir une bonne compréhension du TDD de ce point de vue, j'essaie maintenant de comprendre ma tête autour de TDD dans le monde React / Redux.

Je crois donc que je suis au fond de ce problème maintenant, ce n'était pas tant un problème de test mais plus une copie et une erreur passée commise par un autre membre de l'équipe, je suppose que cela fait partie intégrante de la nécessité d'écrire le tests pour s'adapter au code plutôt que mes méthodes TDD préférées. Merci pour toutes les informations données ici, cela a été extrêmement précieux

Juste pour offrir une autre option qui pourrait fonctionner pour certains cas d'utilisation, j'ai récemment rencontré ce problème avec un composant connecté imbriqué. Plutôt que de créer une maquette de magasin, j'ai exporté les versions non connectées de chaque composant pour les tester unitaire sans magasin. Quelque chose comme ça:

class TopLevelImpl extends React.PureComponent {
  // Implementation goes here
}

// Export here for unit testing.  The unit test imports privates and uses TopLevel
// with Enzyme's mount().
export const privates = {
  TopLevel: TopLevelImpl,
}

export const TopLevel = connect(mapStateToProps)(TopLevelImpl)

J'ai fait la même chose pour les composants imbriqués enfants afin que chacun puisse être testé indépendamment.

Ensuite, j'ai pris le composant connecté de niveau supérieur et passé les composants connectés enfants comme accessoires dans la fonction render() application. Lors du test unitaire du composant de niveau supérieur, au lieu de transmettre les composants connectés, je passe simplement des composants simulés pour m'assurer qu'ils sont rendus au bon endroit. Vous pouvez également transmettre les composants non connectés.

Quelque chose comme ça:

// Root render function
render() {
  return (
    <Provider store={store}>
      <TopLevel child1={<Child1 />} child2={<Child2 />} />
    </Provider>
  )
}

J'aime vraiment ton idée. Mais je fais quelque chose comme ça à la place

const props = {
  child1: () => (<div />),
  child2: () => (<div />)
}
<TopLevel {...props} />

Je pense que si vous avez déjà testé child1 et child2, il n'est probablement pas nécessaire de les avoir dans votre composant TopLevel.

Je passe aussi une fonction si j'ai besoin de rendre conditionnellement le composant.

Je ne suis cependant pas votre dernière phrase. Qu'est-ce que les tests ont à voir avec le passage de composants enfants dans un autre composant?

Bien. Par exemple, j'ai un composant externe connecté qui utilise un ou quelques composants internes connectés . Dans ce cas, si je veux tester un composant externe, je le fais comme ça

wrapper = mount(<ExternalComponent.WrappedComponent {...props} />)

Cependant, j'ai une erreur selon laquelle mes composants internes ont besoin d'un état pour fonctionner correctement.

Invariant Violation: Could not find "store" in either the context or props

Ensuite, j'ai essayé de passer l' état simulé, etc. Cependant, dans mon cas, je dois faire beaucoup d'initialisation pour chaque composant connecté, y compris les appels asynchrones, initialState, etc. et j'ai eu d'autres erreurs liées à cela.

Je pense que ce sera exagéré de faire toute l'initialisation appropriée pour tous les composants internes connectés si je veux juste tester le composant externe.

J'ai donc décidé de déplacer tous les composants connectés et de les transmettre comme accessoires au composant externe connecté. Et maintenant, je suis en mesure de les remplacer par un div vide lorsque je veux tester.

Oh oui, c'était juste la motivation originale du motif. Donc, pour modifier votre phrase, vous vouliez dire "Je pense que si vous avez déjà testé child1 et child2, alors il n'est pas nécessaire de les passer dans votre composant TopLevel lors du test unitaire."

D'accord.

@StlthyLee , Malheureusement, j'ai rencontré le même problème en essayant de tester un composant qui a des composants connectés imbriqués.

Une solution que j'ai trouvée particulièrement utile consiste à connecter le composant imbriqué au magasin redux en fonction de l'environnement de test.

  • Lors de l'exécution des tests, configurez une variable d'environnement NODE_ENV="test" . Vous pouvez intercepter cette variable et faire en sorte que le composant reçoive les propriétés par défaut. Dans ce cas, le composant n'est pas connecté au magasin et lors du test du composant parent, aucun problème n'apparaît.
  • Dans d'autres cas, connectez le composant. Il s'agit du scénario par défaut lors de l'exécution du composant dans l'application.

a) Tout d'abord, vous aurez besoin d'une fonction d'assistance qui détermine si l'environnement est test :

export default function ifTestEnv(forTestEnv, forNonTestEnv) {
  const isTestEnv = process && process.env && process.env.NODE_ENV === 'test';
  const hoc = isTestEnv ? forTestEnv : forNonTestEnv;
  return function(component) {
    return hoc(component);
  };
}

b) Ensuite, defaultProps() et connect() doivent être appliqués conditionnellement en fonction de l'environnement:

import React, { Component } from 'react';
import { connect } from 'react-redux';
import defaultProps from 'recompose/defaultProps';

import ifTestEnv from 'helpers/if_test_env';
import { fetch } from './actions';

class MyComponent extends Component {
   render() {
      const { propA, propB } = this.props;
      return <div>{propA} {probB}</div>
   }

   componentDidMount() {
      this.props.fetch();
   }
}

function mapStateToProps(state) {
  return {
    propA: state.valueA,
    propB: state.valueB
  };
}

export default ifTestEnv(
  defaultProps({
    propA: 'defaultA',
    propB: 'defaultB',
    fetch() {   }
  }),
  connect(mapStateToProps, { fetch })
)(MyComponent);

c) Assurez-vous que NODE_ENV est "test" lorsque les tests sont exécutés:

  • Lorsque vous utilisez mocha CLI, mettez un préfixe pour définir l'environnement de test: NODE_ENV='test' mocha app/**/*.test.jsx .
  • En cas de webpack, appliquez un plugin:
plugins: [
    // plugins...
    new webpack.DefinePlugin({
      'process.env': {
        NODE_ENV: JSON.stringify('test')
      },
    })
]

@markerikson Merci pour les deux suggestions. J'ai essayé le second et emballé mon composant pour le tester avec un fournisseur. Dans ce cas, mapStateToProps fonctionne correctement mais je n'obtiens pas les résultats attendus avec mapDispatchToProps . Ici, le composant testé rend d'autres composants connectés.

Ma mapDispatchToProps ressemble à quelque chose comme

/* <strong i="10">@flow</strong> */
import { bindActionCreators } from 'redux';

import type { Dispatch } from './types';
import * as actions from './actions';

export default (dispatch: Dispatch, ownProps: Object): Object => ({
  actions: bindActionCreators(actions, dispatch),
});

ici la valeur des actions retournées est undefined .

store.js

import { applyMiddleware, compose, createStore } from 'redux';
import { autoRehydrate } from 'redux-persist';
import thunk from 'redux-thunk';

import rootReducer from './reducers';
import { REHYDRATE } from 'redux-persist/constants';

const store = compose(autoRehydrate(), applyMiddleware(thunk, createActionBuffer(REHYDRATE)))(createStore)(rootReducer);

export default store;

Je me rends compte que c'est un fil assez ancien, mais j'ai trouvé que c'était un peu un problème gênant et j'ai pensé que cette solution pourrait aider quelqu'un qui trébucherait sur ce fil (comme moi).

Comme je ne testais en fait rien lié à Redux, une solution simple que j'ai trouvée était de simuler la fonction de connexion pour ne renvoyer que le composant non modifié qui lui avait été transmis.

Par exemple, dans votre application, vous pouvez avoir un composant:


export class ExampleApp extends Component {
  render() {
  }
}

export default connect(
  null,
  { someAction }
)(ExampleApp);

puis au début de votre fichier de test, vous pouvez placer une fonction de connexion simulée pour arrêter Redux d'interagir avec votre composant:

jest.mock("react-redux", () => {
  return {
    connect: (mapStateToProps, mapDispatchToProps) => (
      ReactComponent
    ) => ReactComponent
  };
});

Cette moquerie devra être placée dans chaque fichier de test qui monte un enfant connecté.

@scottbanyard comment

Cette page vous a été utile?
0 / 5 - 0 notes

Questions connexes

rui-ktei picture rui-ktei  ·  3Commentaires

captbaritone picture captbaritone  ·  3Commentaires

benoneal picture benoneal  ·  3Commentaires

mickeyreiss-visor picture mickeyreiss-visor  ·  3Commentaires

vslinko picture vslinko  ·  3Commentaires