Redux: Impossible de référencer des conteneurs enveloppés dans un fournisseur ou en se connectant avec Enzyme

Créé le 20 mars 2016  ·  51Commentaires  ·  Source: reduxjs/redux

Je n'arrive pas à faire référence à quoi que ce soit enveloppé dans un <Provider> et un connect

// test
let component = shallow(<Provider store={store}><ContainerComponent /></Provider>);
component.find('#abc'); // returns null

let component = shallow(<Provider store={store}><div id="abc"></div></Provider>);
component.find('#abc'); // returns the div node

// ContainerComponent
const Component = ({...}) => (<div id="abc"></div>);
export default connect(..., ...)(Component);

J'ai suivi: https://github.com/reactjs/redux/issues/1481 aux exemples où les tests ont été écrits avec une enzyme, cependant, les conteneurs ne sont jamais testés dans ceux-ci. Je ne sais donc pas si je dois/pouvais tester les conteneurs intelligents ?

@fshowalter Des idées ?

Commentaire le plus utile

Même si ce n'est pas directement lié à Redux. J'ai résolu ce problème en appelant à nouveau shallow() sur le conteneur. Il rend le composant interne avec l'état du magasin qui lui est transmis.

Exemple:

import React from 'react';
import {expect} from 'chai';
import {shallow} from 'enzyme';
import configureMockStore from 'redux-mock-store';
import thunk from 'redux-thunk';
import events from 'events';

const mockStore = configureMockStore([ thunk ]);
const storeStateMock = {
  myReducer:{
    someState: 'ABC'
  }
};

let store;
let component;
describe('tests for MyContainerComponent', () => {
  beforeEach(() => {
    store = mockStore(storeStateMock);
    component = shallow(<MyContainerComponent store={store} />).shallow();
  });

  it('renders container', () => {
    expect(component.find('div.somediv')).to.have.length.of(1);
  });
  ...
});

J'espère que cela t'aides

Tous les 51 commentaires

Ni connect() ni Provider ne font partie de cette bibliothèque. Il est plus facile de discuter et de suivre ces problèmes lorsqu'ils sont plutôt classés dans le référentiel approprié : https://github.com/reactjs/react-redux.

Je pense que cela a du sens. Étant donné que vous effectuez un rendu superficiel, seul le composant Provider sera rendu - votre ContainerComponent sera simplement laissé dans son formulaire de sortie d'objet ou tout ce que le rendu superficiel produit.

Deux réflexions ici :

Tout d'abord, notez que le composant wrapper généré par connect() recherche réellement props.store avant de rechercher context.store , donc si vous avez réellement envie de tester un composant connecté , vous devriez pouvoir afficher <ConnectedComponent store={myTestStore} /> sans avoir à vous soucier du fournisseur, du contexte ou de quoi que ce soit.

La deuxième question est de savoir si vous devez vraiment vous soucier de tester réellement le composant entièrement connecté. L'argument que j'ai vu est que si vous pouvez tester votre composant "simple" avec des accessoires spécifiques, et que vous pouvez tester votre implémentation mapStateToProps , vous pouvez supposer en toute sécurité que react-redux les assemblera correctement, et n'avez pas besoin de tester la version connectée elle-même.

@gaearon tu as raison, désolé. Je ne savais pas s'il fallait augmenter cela dans le dépôt de réaction ou d'enzyme.

@markerikson La raison du test du composant intelligent est pour mapToDispatchProps où je voulais m'assurer que le bon répartiteur était appelé par le composant enveloppé. Le simple fait de passer le magasin dans le ConnectedComponent signifie que je ne testerai pas les fonctions de mappage d'état ou de rappel de répartition.

Je vais y réfléchir davantage et soulever un problème dans le dépôt correspondant si j'en ai besoin. Merci pour l'aide.

@mordra : quand vous dites "le bon répartiteur a été appelé", voulez-vous dire "le bon créateur d'action" ?

Vous pourriez faire quelque chose comme ça (la syntaxe est probablement désactivée, mais vous devriez avoir l'idée):

let actionSpy = sinon.spy();

let wrapper = shallow(<PlainComponent someActionProp={actionSpy} />);
wrapper.find(".triggerActionButton").simulate("click");
expect(actionSpy.calledOnce).to.be.true;
expect(actionSpy.calledWith({type : ACTION_I_WAS_EXPECTING)).to.be.true;

En d'autres termes, rendez votre composant ordinaire, transmettez des espions pour les props qui auraient été des actions renvoyées par mapDispatch et déclenchez le comportement dont le composant a besoin pour lui faire appeler ces actions.

De plus, selon mon commentaire précédent: vous devriez pouvoir tester vos mapStateToProps et mapDispatchToProps séparément, et être sûr que React-Redux les appellera de manière appropriée, sans avoir à essayer de tester la version connectée lui-même pour vérifier que c'est le cas.

@markerikson Je ne peux pas faire ce que vous suggérez parce que mon PlainComponent ne connaît pas les actions ou les créateurs d'action. Je ne sais pas si c'est la bonne façon de le faire, mais si vous regardez:
https://github.com/mordra/cotwmtor/blob/master/client/charCreation/charCreation.jsx
vous verrez que mon mapDispatchToProps contient toute la logique :

const mapDispatchToProps = (dispatch) => {
  return {
    onCompleted      : (player) => {
      Meteor.call('newGame', player, function (data) {
        console.log('new game return: ' + data);
      });
      dispatch(routeActions.push('/game'));
      dispatch({type: "INIT_GAME", map: generateAreas(), buildings: generateBuildings(dispatch)});
    },
    onCancelled      : () => dispatch(routeActions.push('/')),
    onChangeName     : input => dispatch(actions.changeName(input)),
    onChangeGender   : gender => dispatch(actions.setGender(gender)),
    onSetDifficulty  : lvl => dispatch(actions.setDifficulty(lvl)),
    onChangeAttribute: (attr, val) => dispatch(actions.setAttribute(attr, val))
  }
};

donc si j'espionnais les accessoires passés dans le PlainComponent, je ne serais pas en mesure de tester si les bonnes actions sont appelées, mais plutôt, seuls les paramètres passés aux créateurs d'action sont corrects.
Je devrais alors tester le composant connect séparément.

Ah. Cela m'aide à mieux comprendre.

Donc, avec la mise en garde que j'ai en fait très peu d'expérience pratique dans l'écriture de tests, quelques réflexions :

  • Ce que vous avez réellement là-bas, ce sont vraiment des créateurs d'action, ils ne sont tout simplement pas définis séparément parce que vous voulez accéder à dispatch . Ce n'est pas très facilement testable en soi. Vous voudriez probablement également pouvoir tester leur comportement, et pour ce faire, vous voudriez les définir comme leurs propres fonctions en dehors de mapDispatch .
  • Ceux-ci semblent tous être de très bons candidats à utiliser avec redux-thunk, ce qui faciliterait certainement la définition de ces fonctions par eux-mêmes et leur permettrait d'accéder à dispatch .
  • Votre PlainComponent _does_ connaît les "actions", ou du moins les rappels dans ce cas, dans la mesure où vous les transmettez, et quelque part dans votre composant, vous exécutez this.props.onSetDifficulty("HARD") ou quelque chose comme ça. Quand j'ai suggéré de passer des espions pour des actions, c'est le genre de chose que je suggérais de remplacer. Ainsi, si vous transmettez un espion pour onSetDifficulty , vous pouvez vérifier que votre composant l'a appelé et a transmis une valeur acceptable pour le niveau de difficulté.

En fin de compte, je pense que vous devriez pouvoir rendre les choses beaucoup plus testables en prenant ces fonctions définies dans mapDispatch et en les définissant séparément. Ensuite, vous n'aurez pas à vous soucier d'avoir mapDispatch connecté pour tester correctement votre composant, et vous pourrez vous concentrer sur le fait que le composant vient d'appeler ou non un rappel de prop donné avec les bons arguments ou quelque chose du genre.

De plus, du point de vue du composant : en fin de compte, il ne s'inquiète pas vraiment de savoir si {action: CHANGE_NAME, name : "Fred"} a été envoyé. Tout ce qu'il sait, c'est qu'il a appelé this.props.onChangeName("Fred") , et _c'est ce que vous devriez essayer de tester - qu'il a appelé un rappel d'accessoire de la bonne manière.

@mordra dans votre cas, j'exporterais mapDispatchToProps et le testerais isolément. Vous pouvez vérifier que les noms de propriété sont tous corrects et envoyer un espion pour qu'il teste vos créateurs d'action.

Cela dit, j'ai tendance à éviter mapDispatchToProps au profit de mapActionCreatorsToProps qui cartographie des créateurs d'action déjà formés que je peux facilement tester isolément. Dans ce cas, je teste simplement que tous mes accessoires sont définis pour se prémunir contre les fautes de frappe d'importation.

Enfin, notez que vous pouvez utiliser un rendu normal (pas superficiel). Vous auriez besoin de jsdom ou d'un vrai navigateur pour cela, mais vous pouvez alors rendre n'importe quel niveau en profondeur.

Je ne savais pas s'il fallait augmenter cela dans le dépôt de réaction ou d'enzyme

Désolé, je ne voulais pas dire React ou Enzyme repos. Je voulais dire React Redux qui est la bibliothèque que vous utilisez.

Merci pour l'aide les gars, c'est beaucoup d'informations pour moi d'aller et de digérer. @markerikson @fshowalter J'ai fait quelques recherches supplémentaires et je prendrai votre suggestion d'exporter le mapState/Dispatch et de les tester séparément, il est logique de tester les rappels qui évoquent les actions attendues.

@gaearon Le composant sans état ne s'affiche pas sans être superficiel et en survolant simplement les problèmes, il semble y avoir des problèmes pour rendre les composants avec état qui ont des composants sans état imbriqués, j'ai donc pris note mentalement d'éviter ce chemin pour l'instant.

Je ne sais pas à quels problèmes vous faites référence. Vous pouvez très bien utiliser un rendu superficiel avec des composants fonctionnels. Le rendu superficiel ne fonctionne cependant qu'à un niveau de profondeur (quelle que soit la définition du composant). C'est sa principale caractéristique : il ne se répète pas, les tests de composants restent donc indépendants. Si vous rencontrez des problèmes spécifiques avec le rendu peu profond que vous pouvez reproduire, veuillez signaler les bogues par rapport au référentiel React. Merci!

@gaearon : donc pour clarifier, si je fais ça :

let wrapper = shallow(<A><B /></A>);

B sera-t-il rendu ou non ? Parce que je pense que c'était la question initiale - essayer de rendre un <Provider> autour d'un composant connecté afin de tester la connexion.

Même si ce n'est pas directement lié à Redux. J'ai résolu ce problème en appelant à nouveau shallow() sur le conteneur. Il rend le composant interne avec l'état du magasin qui lui est transmis.

Exemple:

import React from 'react';
import {expect} from 'chai';
import {shallow} from 'enzyme';
import configureMockStore from 'redux-mock-store';
import thunk from 'redux-thunk';
import events from 'events';

const mockStore = configureMockStore([ thunk ]);
const storeStateMock = {
  myReducer:{
    someState: 'ABC'
  }
};

let store;
let component;
describe('tests for MyContainerComponent', () => {
  beforeEach(() => {
    store = mockStore(storeStateMock);
    component = shallow(<MyContainerComponent store={store} />).shallow();
  });

  it('renders container', () => {
    expect(component.find('div.somediv')).to.have.length.of(1);
  });
  ...
});

J'espère que cela t'aides

Devrions-nous créer un moyen plus accessible de le faire via un module personnalisé peut-être ?

Edit : Nous pouvons donc déclarer connect comme une méthode pour Enzyme, telle que component = shallowWithConnect()

@ev1stensberg C'est une excellente idée. Avons-nous décidé s'il s'agit de l'approche et/ou a-t-on commencé à travailler là-dessus ? Sinon, j'aimerais contribuer.

Pour tous ceux qui tomberont sur ce problème à l'avenir, voici l'approche qui fonctionne pour moi : il suffit de tester le composant ordinaire par lui-même. Exportez à la fois la définition du composant en tant qu'exportation nommée et le composant connecté (à utiliser dans votre application) en tant qu'exportation par défaut. Revenons au code de la question d'origine :

// test
let component = shallow(<Provider store={store}><ContainerComponent /></Provider>);
component.find('#abc'); // returns null

let component = shallow(<Provider store={store}><div id="abc"></div></Provider>);
component.find('#abc'); // returns the div node

// ContainerComponent
export const Component = ({...}) => (<div id="abc"></div>); // I EXPORT THIS, TOO, JUST FOR TESTING
export default connect(..., ...)(Component);

Alors fais juste

const component = shallow(<Component {...props} />)
// test component

Et comme d'autres l'ont souligné ci-dessus, si vous avez également une couverture pour les fonctions que vous transmettez à connect, vous devriez avoir une couverture complète pour votre composant dans son ensemble.

Eh bien, la première chose à noter ici est la philosophie du :

  1. Communiquer l'état du magasin à un
  2. Modifier l'état en répartissant des actions en fonction de la événements.

Si je suis d'accord, vous n'avez qu'à tester 2 choses :

  1. votre composant reçoit-il les accessoires corrects générés à partir de l'état (peut-être via des sélecteurs) ?
  2. votre composant envoie-t-il les bonnes actions ?

C'est donc mon approche

import React from 'react';
import { shallow } from 'enzyme';
import { fromJS } from 'immutable';
import { createStore } from 'redux';
// this is the <Map /> container
import Map from '../index';
// this is an action handled by a reducer
import { mSetRegion } from '../reducer';
// this is an action handled by a saga
import { sNewChan } from '../sagas';
// this is the component wrapped by the <Map /> container
import MapComponent from '../../../components/Map';

const region = {
  latitude: 20.1449858,
  longitude: -16.8884463,
  latitudeDelta: 0.0222,
  longitudeDelta: 0.0321,
};
const otherRegion = {
  latitude: 21.1449858,
  longitude: -12.8884463,
  latitudeDelta: 1.0222,
  longitudeDelta: 2.0321,
};
const coordinate = {
  latitude: 31.788,
  longitude: -102.43,
};
const marker1 = {
  coordinate,
};
const markers = [marker1];
const mapState = fromJS({
  region,
  markers,
});
const initialState = {
  map: mapState,
};
// we are not testing the reducer, so it's ok to have a do-nothing reducer
const reducer = state => state;
// just a mock
const dispatch = jest.fn();
// this is how i mock the dispatch method
const store = {
  ...createStore(reducer, initialState),
  dispatch,
};
const wrapper = shallow(
  <Map store={store} />,
);
const component = wrapper.find(MapComponent);

describe('<Map />', () => {
  it('should render', () => {
    expect(wrapper).toBeTruthy();
  });

  it('should pass the region as prop', () => {
    expect(component.props().region).toEqual(region);
  });

  it('should pass the markers as prop', () => {
    expect(component.props().markers).toEqual(markers);
  });
  // a signal is an action wich will be handled by a saga
  it('should dispatch an newChan signal', () => {
    component.simulate('longPress', { nativeEvent: { coordinate } });
    expect(dispatch.mock.calls.length).toBe(1);
    expect(dispatch.mock.calls[0][0]).toEqual(sNewChan(coordinate));
  });
  // a message is an action wich will be handled by a reducer
  it('should dispatch a setRegion message', () => {
    component.simulate('regionChange', otherRegion);
    expect(dispatch.mock.calls.length).toBe(2);
    expect(dispatch.mock.calls[1][0]).toEqual(mSetRegion(otherRegion));
  });
});

@markerikson que vouliez-vous dire par :

vous pouvez tester votre implémentation mapStateToProps

disons que vous exportez le conteneur connecté via une exportation ES6. Vous n'auriez pas accès au mapStateToProps privé. Parliez-vous d'une autre manière ou pouvez-vous être précis?

@mordrax

Vous pouvez vérifier que les noms de propriété sont tous corrects

Donc, si vous exposez votre mapStateToProps et le rendez public, dites que vous rendez votre ContainerComponent à partir de votre test, y compris l'envoi, par exemple, d'un faux magasin et l'envoi d'un accessoire d'état, puis votre mapStateToProps reçoit cet état, le mappe à un accessoire. Mais alors, comment pouvez-vous le tester à partir de là ? Connect appellera mapStateToProps fusionnant ainsi les accessoires, mais où est la couture et quel point est la couture dans le code pendant ce processus/flux où vous pouvez intercepter les accessoires définis sur le composant de présentation ? Je suppose que le double creux comme @ gatedude2 pourrait être un moyen de vérifier les accessoires cartographiés sans aucune couture ...

également sur votre peu profond. peu profond. Donc, le composant connect() renvoie quoi, un wrapper, n'est-ce pas ? Et cet emballage, qu'est-ce qui enveloppe le composant de présentation ?

@granmoe pouvez-vous expliquer cela plus en détail quant à ce que vous voulez dire exactement :

si vous avez également une couverture pour les fonctions que vous transmettez à connect, vous devriez avoir une couverture complète pour votre composant dans son ensemble.

Je suis également avec @tugorez en termes de _ce_ que je veux tester et pourquoi.

@markerikson si bel exemple avec l'espion. C'est une chose que vous pouvez tester, mais vous auriez besoin de plus d'asserteurs pour tester "l'unité de comportement". Le simple fait de tester qu'une action d'espionnage a été appelée depuis mapDispatchToProps ne nous dit pas vraiment toute l'histoire du composant conteneur. Il n'affirme pas le résultat attendu en fonction de la logique du gestionnaire de votre conteneur.

Il ne suffit pas de tester que vous avez passé les accessoires ou l'état, vous voulez tester le comportement du composant conteneur. Cela signifie tester deux choses :

1) le composant conteneur a-t-il relié les accessoires au composant de présentation et si c'est le cas, y a-t-il un certain état que je m'attends à ce que le composant de présentation ait une fois qu'il est rendu en fonction de cet état spécifique transmis par les accessoires au composant de présentation. Qui sait, peut-être qu'un développeur stupide arrive et ajoute plus de code à mapStateToProps, vous ne pouvez pas toujours croire qu'il mappera les choses correctement, c'est le but de tester la logique du conteneur. Bien qu'il n'y ait pas vraiment de logique dans mapStateToProps, qui sait encore qu'un développeur arrive et annonce une instruction if là-dedans... eh bien, c'est un comportement qui pourrait gâcher les choses.

2) la logique du gestionnaire de répartition (comportement) fonctionne-t-elle dans le conteneur.

@dschinkel :

La pratique typique pour définir les composants connectés à Redux consiste à export default connect()(MyComponent) , ainsi qu'à export MyComponent en tant qu'exportation nommée. De même, comme mapState n'est qu'une fonction, vous pouvez également export const mapState = () => {} , puis l'importer et la tester séparément.

En ce qui concerne le test du "composant conteneur" par rapport au "composant de présentation": l'idée générale ici est que vous n'avez probablement pas besoin de vous soucier de tester le conteneur pour la plupart, où "conteneur" === "la sortie de connect ". React-Redux dispose déjà d'une suite de tests complète sur la façon dont cela devrait se comporter, et il n'y a aucune raison pour que vous dupliquiez cet effort. Nous _savons_ qu'il gère correctement l'abonnement au magasin, en appelant mapState et mapDispatch , et en passant les props au composant enveloppé.

Ce qui _vous_ en tant que consommateur de la bibliothèque va s'intéresser, c'est au comportement de _votre_ composant. Peu importe que votre propre composant reçoive des accessoires d'un wrapper connect , d'un test ou de quelque chose d'autre - c'est simplement la façon dont ce composant se comporte compte tenu d'un certain ensemble d'accessoires.

(De plus, FWIW, je réalise que vous êtes absolument l'expert en matière de tests et je ne le suis pas, mais il semble que vous deveniez un peu paranoïaque à propos de certaines choses :) Si vous êtes préoccupé par un mapState être accidentellement cassé, puis écrivez des tests pour cela et passez à autre chose.)

Maintenant, si votre composant enveloppé rend ensuite d'autres composants connectés à l'intérieur de celui-ci, et surtout si vous faites un rendu complet au lieu d'un rendu superficiel, alors il peut très bien être plus simple de tester les choses en faisant const wrapper = mount(<Provider store={testStore}><MyConnectedComponent /></Provider> . Mais, dans l'ensemble, mon point de vue est que la plupart du temps, vous _devriez_ pouvoir tester votre fonction mapState et le composant "plain" séparément, et vous ne devriez vraiment pas essayer de tester la version connectée juste pour vérifier que la sortie de mapState est transmise au composant plain.

Pour référence, vous voudrez peut-être regarder la version miniature de connect que Dan a écrite il y a quelque temps pour illustrer ce qu'il fait en interne : https://gist.github.com/gaearon/1d19088790e70ac32ea636c025ba424e .

Oui, je veux dire si vous exportez mapStateToProps et dispatchStateToProps, ce serait mieux. Je ne veux pas tester que connect() (le collaborateur dans mon test) fonctionne, certainement pas. Ce serait donc une façon claire de le faire, exportez ces deux méthodes :

exemple-container-spec.js

describe('testing mapPropsToState directly', () => {
    it.only('returns expected state', () => {
        const state = {firstName: 'Dave'};
        const output = mapStateToProps(state);

        expect(output.firstName).to.equal('Dave');
    });
});

Récipient

import React from 'react';
import { connect } from 'react-redux'
import Example from './Example';

export const mapStateToProps = (state) => ({
    firstName: state.firstName
})

export default connect(mapStateToProps)(Example);

Présentation

import React, { Component } from 'react';

export default class Example extends Component {
    render(){
        return (
            <div className='example'>
                {this.props.firstName}
            </div>
        )
    }
}

Là encore, vous pouvez également faire quelque chose comme ça avec le rendu _shallow_ et toujours plonger dans le composant enfant :

exemple-container-spec.js

it('persists firstName to presentation component', () => {
        var state = {firstName: "Dave"};
        const container = mount(<ExampleContainer store={fakeStore(state)}/>),
            example = container.find(Example);

        expect(example.length).to.equal(1);
        expect(example.text()).to.equal(state.firstName);
    });

Récipient

import React from 'react';
import { connect } from 'react-redux'
import Example from './Example';

const mapStateToProps = (state) => ({
    name: state.firstName
})

export default connect(mapStateToProps)(Example);

Présentation

import React, { Component } from 'react';

export default class Example extends Component {
    render(){
        return (
            <div className='example'>
                {this.props.firstName}
            </div>
        )
    }
}

encore une autre façon de le faire superficiellement, est de faire un double superficiel. Shallow sur le parent peu profond rendra peu profond le composant enfant qui l'enveloppera, quelque chose comme ceci :

it('get child component by double shallow()', () => {
        const container = shallow(<ExampleContainer store={fakeStore({})}/>),
            presentationalChildComponent = container.find(Example).shallow().find('.example');

        expect(presentationalChildComponent).to.have.length(1);
    });

C'est donc juste une autre façon de vérifier le résultat de votre composant de présentation.

Quoi qu'il en soit, cela fonctionnerait ... mais comme vous l'avez dit, l'exportation de mapStateToProps est probablement la meilleure, car c'est tout ce qui m'importe de tester en termes de définition de l'état des accessoires.

Je me rends compte que vous êtes absolument l'expert en matière de tests et je ne le suis pas, mais il semble que vous deveniez un peu paranoïaque à propos de certaines choses :)

non, je ne le suis pas, tester correctement signifie qu'il est important d'avoir de bons tests qui ne sont pas fragiles et qui apportent de la valeur . Tout d'abord, personne n'est un _expert_. Tout le monde apprend continuellement, certains ne veulent tout simplement pas l'admettre à cause de leur ego. C'est ce qu'on appelle l'artisanat logiciel (alias professionnalisme).

Tester uniquement le comportement et ne pas tester les collaborateurs (connect() lui-même) comme vous l'avez dit est important. Comprendre cela pour la première fois et bien tester est important. Il est important de s'assurer que vous ne testez pas des collaborateurs et cela nécessite parfois des discussions entre collègues développeurs.

Vous pouvez avoir des tests fragiles, et il est important de ne pas avoir de tests fragiles. Les tests doivent être pris au sérieux, ce qui signifie vérifier en permanence si vous vous en approchez correctement. J'essaie simplement de comprendre mon flux. Je TDD, donc comment je commence, est important, et de bons tests sont importants avec ça. Ainsi, personne ne devrait se sentir "paranoïaque" lorsqu'il cherche à comprendre différentes façons de tester les composants React.

Nouveau React Test Repo à venir...
En fait, je vais bientôt partager un dépôt qui montre divers styles, combinaisons et approches pour tester react-redux. Je pense que ça va aider les gens parce que je ne suis pas le seul à y penser. Maintes et maintes fois, vous voyez des gens poser les mêmes questions sur le test du conteneur redux et d'autres composants. Les docs Redux et les docs react-redux ne rendent pas justice à cela, ils manquent de documentation dans la zone de test IMO. Cela ne fait qu'effleurer la surface, et nous avons besoin d'un joli référentiel qui montre différents styles d'approche des tests React, donc je le mettrai bientôt en place.

If anyone would like to contribute to that repo, please get a hold of me pour que je puisse vous ajouter en tant que collaborateur. J'aimerais que les gens ajoutent des exemples. J'aimerais voir des exemples avec React Test Utils + mocha , avec Enzyme , Ava , Jest , tape , etc.

Bilan :

Je pense que les deux approches dont nous avons discuté sont bonnes. Testez directement la méthode, ou testez-la comme je l'ai fait ci-dessus, plongez-y. Parfois, vous ne voulez pas baser vos tests sur une méthode spécifique car vous pouvez obtenir des tests fragiles... d'où la raison pour laquelle les gens se sont fait piquer dans le passé avec des tests. Donc, que ce soit ou non pour tester autour d'une fonction, cela dépend de savoir si cela teste "l'unité". Parfois, vous ne voulez pas rendre une méthode publique, et il peut être préférable de tester le contrat ci-dessus et de garder une partie privée. Il est donc toujours important de penser à ce que vous faites souvent lorsque vous écrivez des tests. Aucun problème avec cela. Écrire de bons tests n'est pas facile.

C'est exactement ce que TDD promeut, le faire souvent. Pour que vous vous retrouviez avec un code plus léger, meilleur et moins fragile. TDD vous oblige à penser à tester à l'avance, pas plus tard. C'est la différence et pourquoi les gens comme moi veulent comprendre différents flux et approches parce que je dois le faire quand je TDD, cela m'oblige à réévaluer souvent la conception par petits morceaux, et cela signifie penser souvent à mon approche de test.

@markerikson merci pour vos contributions, j'adore parler de test. Bon truc mec. Je ne remettais pas en cause votre expertise, juste que je n'avais pas essayé de tester mapStateToProps directement moi-même ! Vous ne faites pas mal d'être un non-testeur ;). Je suis tout simplement nouveau sur redux, aussi simple que cela, j'aurai donc des questions qui ne font pas que "toucher la surface". Nous devrions discuter des tests à un niveau inférieur afin que les gens comprennent ce qu'ils font, comment ils peuvent le faire et s'ils tirent profit de leur approche. Pour ce faire, vous devez comprendre connect, react-redux, provider à un niveau inférieur... afin de savoir comment "tester uniquement le comportement". Je n'ai pas vu beaucoup de gens exporter mapStateToProps, ce qui m'intéresse aussi... et je me demande pourquoi pas.

WeDoTDD.com
BTW pour toute personne intéressée, j'ai lancé WeDoTDD.com l'année dernière (écrit en React btw). Si une équipe spécifique (tous les développeurs d'une équipe particulière) TDD, ou toute votre entreprise (certaines entreprises ont des développeurs où tous les TDD, en particulier les sociétés de conseil), veuillez me contacter sur slack.wedotdd.com

Mettre à jour.

après avoir joué davantage avec les tests de composants connectés et réfléchi à ce qui fonctionne bien pour moi, je suis maintenant à 100 % d'accord avec la déclaration de @markerikson ici :

si vous avez réellement envie de tester un composant connecté, vous devriez être en mesure de rendre sans avoir à se soucier du fournisseur ou du contexte ou de quoi que ce soit.

Je n'ai pas vu le besoin d'introduire <Provider /> dans mes tests et il semble que vous testiez un collaborateur si vous le faites alors que le but n'est pas de tester que <Provider /> fonctionne. Vous devez tester que le comportement de votre composant fonctionne et peu importe si votre conteneur obtient le magasin via context. Passez simplement le magasin en tant qu'accessoire..connect a juste besoin d'un moyen d'accéder au magasin, peu importe comment (contexte ou accessoires)...débarrassez-vous complètement du fournisseur dans vos tests.

Je ne vois pas non plus la nécessité d'utiliser l'option de contexte d'enzyme, qui est une autre façon de conserver le magasin via le contexte de React. Oubliez complètement le contexte de réaction dans vos tests, c'est un ballonnement totalement inutile et rend vos tests plus complexes et beaucoup plus difficiles à maintenir et à lire.

Testez votre fonction pure mapStateToProps directement en l'exportant dans votre fichier conteneur. J'utilise une combinaison de tests mapStateToProps + également quelques tests qui testent mon conteneur de manière superficielle, en vérifiant que le composant de présentation reçoit au moins les accessoires que j'attends, puis je m'arrête là avec les tests d'accessoires. J'ai également décidé de ne pas faire de double immersion pour mes tests de composants de conteneurs. TDD me dit qu'en essayant de le faire, les tests deviennent trop compliqués et que vous faites probablement quelque chose de mal. Ce "faux" est "ne doublez pas les choses peu profondes pour vos tests de composants de conteneur". Créez plutôt une nouvelle suite de tests de composants de présentation qui testent indépendamment la sortie de vos composants de présentation.

Heureux d'apprendre que vous avez réglé les choses. (FWIW, mes commentaires étaient en grande partie en réponse à vos questions sur les "coutures" et "creuser dans les accessoires", qui semblaient vraiment aller à des niveaux de détail inutiles pour peu ou pas d'avantages.)

L'utilisation d'un <Provider> /context _est_ nécessaire si vous effectuez un rendu complet d'un composant qui rend d'autres composants connectés, car ces enfants connectés rechercheront le magasin en contexte.

Si vous avez des suggestions pour améliorer les documents actuels de Redux sur les tests, déposez certainement un PR.

@markerikson bon point sur la monture. Je ne ferais un montage que si j'avais besoin de tester le cycle de vie du composant que je teste. Je ne l'utiliserais pas pour plonger en profondeur dans les composants enfants ... qui ne seraient plus considérés comme un test unitaire, et sont essentiellement des tests d'intégration et vous coupleriez ce test aux détails de mise en œuvre lorsque vous devriez tester ces composants enfants sur leur propre de manière isolée, et non via un composant parent.

Quoi qu'il en soit, de bonnes choses, merci!

D'accord, en disant simplement que si vous _utilisez_ mount pour vérifier le cycle de vie du composant en question, et que ce composant rend d'autres composants connectés, alors vous _aurez_ besoin du magasin disponible en contexte pour ces composants imbriqués afin d'éviter les erreurs lorsque le composant testé est rendu.

(edit : je suis apparemment en train de me répéter, désolé - les risques de répondre à des questions à plusieurs endroits sans toujours revenir sur un fil, ou même faire défiler un peu vers le haut.)

ah ok j'ai compris ! oui j'y avais pas pensé...

Merci pour tous les commentaires ici, @markerikson et @dschinkel ! Ils sont très utiles. J'ai maintenant décidé d'exporter mon composant non connecté et de le tester comme je le ferais avec n'importe quel autre, et également de tester mes mapStateToProps et mapDispatchToProps séparément. Cependant, il y a un cas où ces tests n'attrapent pas, et c'est lorsque map(State/Dispatch)ToProps n'est pas réellement passé dans l'appel à connect . Exemple:

import React from 'react';
import { connect } from 'react-redux';

export const mapStateToProps = ({ name }) => ({ name });

export const Example = ({ name }) => <h1>{name}</h1>;

export default connect({ name } => ({ name: firstName }))(Example); // Whoops, didn't actually pass in `mapStateToProps`.

Cela ne se produira probablement jamais dans la pratique, mais cela pourrait, d'autant plus que les linters ne signaleraient pas mapStateToProps comme une variable inutilisée puisqu'elle est exportée.

@danny-andrews Pourriez-vous être plus précis sur la façon de tester votre fonction mapDispatchToProps ?

@leizard

Je ne teste plus mapDispatchToProps ou mapStateToProps directement. J'ai changé d'avis depuis ce fil et je conseille de ne pas le faire.

En les testant également, vous rencontrerez le problème de @danny-andrews, mais le problème concerne davantage les bons tests et essayer d'exposer ces deux fonctions n'est pas une bonne idée. C'est fini les tests. Vous devriez tester ces deux méthodes indirectement en testant le comportement ou en testant les accessoires via votre conteneur peu profond à la place. J'ai trouvé qu'il n'y avait aucune raison d'essayer d'exposer ces méthodes privées et j'ai maintenant réalisé que c'était aussi une mauvaise pratique de test d'essayer de le faire.

Je ne suis donc pas d'accord avec @markerikson , testez via votre composant connecté.

Trouver la bonne API/contrat pour tester et ne pas surtester, c'est la clé bébé :).

@dschinkel

Vous devriez tester ces deux méthodes indirectement en testant le comportement...
Je ne suis donc pas d'accord avec @markerikson , testez via votre composant connecté.

Suggérez-vous de renoncer entièrement à l'exportation du composant non connecté et de ne tester que le composant connecté ? Je pense que c'est "sur tester". Cela lie inutilement vos tests aux détails d'implémentation du composant qu'il teste. Parce que la fonctionnalité du composant non connecté (s'il en a, c'est une toute autre boîte de Pandore) n'a aucun rapport avec l'endroit d'où il reçoit ses accessoires. Peut-être souhaitez-vous transformer ce composant en un composant purement de présentation à l'avenir. Si vous testez le composant connecté, vous devrez modifier tous vos tests pour ne plus injecter de store mais passer directement les props. Si vous avez testé le composant non connecté, vous n'avez rien d'autre à faire que de supprimer les tests spécifiques au composant connecté (plus d'informations ci-dessous).

Je suis d'accord que vous ne devriez pas tester directement mapStateToProps / mapDispatchToProps , cependant. Exposer ces méthodes privées simplement à des fins de test m'a toujours semblé être une odeur de code. En fait, je pense que c'est la seule fois où vous devez tester le composant connecté. Donc, pour résumer :

  1. Exporter un composant non connecté et un composant connecté
  2. NE PAS exporter mapStateToProps ou mapDispatchToProps
  3. Testez toute la logique des composants via le composant non connecté
  4. Testez uniquement le composant connecté lorsque vous testez l'interaction avec redux (les accessoires de données sont transmis depuis le bon endroit dans le magasin/les accessoires d'action sont attribués aux créateurs d'action appropriés, etc.).

Le seul surcoût supplémentaire avec le numéro 4 est que vous devez supprimer le magasin redux pour le transmettre à votre composant connecté. C'est assez facile, cependant, car son API ne comprend que trois méthodes.

Méthode MapStateToProps

TestContainer.jsx :

import { connect } from 'react-redux';

export const TestContainer = ({ permissions }) => (permissions['view-message'] ? 'Hello!' : null);

export const mapStateToProps = ({ auth }) => ({ permissions: auth.userPermissions });

export default connect(mapStateToProps)(TestContainer); // Ewww, exposing private methods just for testing

TestContainer.spec.jsx :

import React from 'react';
import { shallow } from 'enzyme';

import TestContainer, { mapStateToProps } from './TestContainer';

it('maps auth.userPermissions to permissions', () => {
  const userPermissions = {};

  const { permissions: actual } = mapStateToProps({
    auth: { userPermissions },
  });

  expect(actual).toBe(userPermissions);
});

Nouvelle méthode

TestContainer.jsx :

import { connect } from 'react-redux';

export const TestContainer = ({ permissions }) => (permissions['view-message'] ? 'Hello!' : null);

const mapStateToProps = ({ auth }) => ({ permissions: auth.userPermissions });

export default connect(mapStateToProps)(TestContainer);

TestContainer.spec.jsx :

import React from 'react';
import { shallow } from 'enzyme';

import TestContainer, { TestComponent } from './TestContainer';

it('renders TestComponent with approriate props from store', () => {
  const userPermissions = {};
  // Stubbing out store. Would normally use a helper method. Did it inline for simplicity.
  const store = {
    getState: () => ({
      auth: { userPermissions },
    }),
    dispatch: () => {},
    subscribe: () => {},
  };
  const subject = shallow(<TestContainer store={store} />).find(TestComponent);

  const actual = subject.prop('permissions');
  expect(actual).toBe(userPermissions);
});

oui c'est ce que je dis que je n'exporte pas et ne teste pas mapStateToProps , c'est plus de test. Testez via l' API . L'API ici est le Container .

@danny-andrews

Testez toute la logique des composants via le composant non connecté

pouvez-vous site quelques exemples?

Je veux dire la même chose que vous quand vous avez dit : "Vous devriez tester ces deux méthodes indirectement en testant le comportement"

Cela pourrait aider à clarifier les termes : quand je dis "composant non connecté", je veux dire le composant brut qui est enveloppé dans l'appel à connect et quand je dis "composant connecté", je veux dire le composant résultant qui est renvoyé de l'appel à connect . "Composant connecté" et "conteneur" sont, je crois, synonymes. De mon dessus:

// Unconnected component. Test all component logic by importing this guy.
export const TestComponent = ({ permissions }) => (permissions['view-message'] ? 'Hello!' : null);

const mapStateToProps = ({ auth }) => ({ permissions: auth.userPermissions });

// Connected component (container). Only test interaction with redux by importing this guy.
export default connect(mapStateToProps)(TestComponent);

Certaines personnes préféreraient les diviser entièrement et faire en sorte que leurs "conteneurs" soient simplement des appels à connect qui enveloppent un composant purement de présentation. Dans ce cas, les numéros 1 et 3 de la liste disparaissent et les seuls tests que vous devez écrire sont ceux qui vérifient l'interaction avec redux (numéro 4).

Je prévois d'écrire un article de blog ÉNORME à ce sujet. Je suis occupé pour le moment dans le code mais je répondrai plus tard

Après avoir lu ce fil plusieurs fois, je suis arrivé à la conclusion qu'il y a 3 approches recommandées :

  1. Exportez mapDispatchToProps et mapStateToProps et testez-les séparément
  2. Faites un rendu peu profond du composant Container et testez que le câblage est correct
  3. Utilisez des sélecteurs au lieu de mapStateToProps et des créateurs d'action au lieu de mapDispatchToProps et testez-les séparément ; écrire des tests en utilisant les créateurs d'action, les réducteurs et les sélecteurs ensemble pour s'assurer que l'ensemble du flux fonctionne.

En fin de compte, je suppose que toutes les options sont valables, mais ont leurs propres avantages et inconvénients. Le "plus pur" est probablement le 2ème, mais implique aussi le plus de travail. Ma préférence personnelle irait en fait vers l'option 3.
J'ai écrit plus en détail à ce sujet sur mon blog , où j'ai aussi quelques exemples de code.

@ucorina
Dans le monde actuel des modules ES6, chaque fichier est considéré comme un module enveloppé dans sa propre portée. L'exportation est votre API du petit module et il semble super mal d'exporter des méthodes, juste pour que vous puissiez les tester. C'est similaire à la façon dont vous rendriez publiques les méthodes privées, juste pour les tester.

C'est pourquoi je penche pour l'approche @dschinkel et non pour exporter mapDispatch et mapState, car pour moi, il s'agit d'une implémentation privée du composant connecté.

Cependant, je ne sais pas quoi faire avec un composant, qui ne fait qu'envelopper un autre composant autour d'une connexion.

Avez-vous déjà écrit le billet de blog? @dschinkel aimerait le lire

@AnaRobynn

Ouais, je suis d'accord avec vous que le simple fait d'exporter mapDispatch et mapState se sent mal, et je ne l'utiliserais probablement pas moi-même, mais c'est une option valable, même si elle est moins "pure" . Je l'ai laissé là comme option possible également parce que Dan Abramov a suggéré cette technique exacte ici : https://github.com/reduxjs/react-redux/issues/325#issuecomment -199449298.

Pour répondre à votre question "que faire avec un composant, qui ne fait qu'enrouler un autre composant autour d'une connexion", je dirais simplement de ne pas le tester. Il n'y a pas de logique métier liée à votre application et vous ne voulez pas tester l'implémentation connect - qui est déjà testée dans la bibliothèque react-redux .

Si vous voulez quand même tester, pour pérenniser votre code, vous pouvez toujours suivre la 2ème approche que je décris ici qui est plus proche de ce que @dschinkel décrit.

@ucorina Exactement, je pense que l'option est celle que je choisirais aussi ! Merci d'avoir expliqué votre réponse plus en profondeur. C'est un article de blog bien écrit. Félicitations !

@ucorina Désolé de déranger à nouveau, mais après avoir dormi dessus pendant un moment, je ne suis pas sûr d'être entièrement d'accord avec l'option 2 non plus. Je ne suis pas sûr cependant. Je ne suis pas non plus sûr de l'avantage de tester mapStateToProps.

Pensez-vous que c'est bien que "l'option 2" teste aussi indirectement les créateurs d'action ? Et aussi tester indirectement les sélecteurs ? Est-ce une bonne chose?
Je ne suis pas convaincu à 100% par cela, mais je suis également confus à propos de toutes les (més) informations disponibles. Il y a tellement d'idées différentes à tester.

  1. N'utilisez que des sélecteurs à l'intérieur mapStateToProps

    • se moquer d'eux lors des tests de conteneurs si nécessaire

    • Testez les sélecteurs séparément.

    • Utilisez uniquement des créateurs d'action à l'intérieur mapDispatchToProps

    • se moquer d'eux lors des tests de conteneurs si nécessaire.

    • Testez les créateurs d'action séparément.

    • Faible profondeur du composant connecté

    • affirmer qu'il est rendu

    • tester toute logique supplémentaire qui a dû être ajoutée à mapStateToProps , mapDispatchToProps ou mergeProps et qui n'a pas déjà été testée ailleurs. (envois conditionnels, sélecteurs qui prennent des arguments à partir de ownProps , etc.)



      • dans ce cas, vous ne testez que les simulations qui ont été appelées le bon nombre de fois et avec les bons arguments.



Alternative:

  • État du magasin fictif pour tester ensemble conteneur + sélecteurs
  • Simulation d'appels d'API externes pour tester ensemble conteneur + actions

@AnaRobynn Je crois que la 2ème approche de @ucorina est la bonne.

Beaucoup de ce que j'entends suggère:

  1. Vous devez exposer mapDispatchToProps et le tester directement
  2. Vous n'avez pas besoin de tester votre appel à connect() car connect() est déjà testé

Concernant 1, je ne pense pas que ce soit une bonne pratique d'exposer les composants internes afin de tester. Vos tests A : assument maintenant la structure interne, et B : devraient tester la chose comment ils vont réellement être utilisés.

Concernant 2, vous devriez _absolument_ tester quelque chose qui appelle une bibliothèque externe. C'est l'un des points faibles de toute application de rupture. Avec n'importe quelle bibliothèque, il est toujours possible qu'un changement de rupture soit introduit dans une version mineure/corrective, et vous _voulez_ que vos tests échouent rapidement.

Pensez-vous que c'est bien que "l'option 2" teste aussi indirectement les créateurs d'action ? Et aussi tester indirectement les sélecteurs ? Est-ce une bonne chose?

Oui, une couverture de test redondante est une bonne chose.

@philihp @dougbacelar
Je crois que j'ai grandi un peu lié au test des applications React. Mes convictions et réflexions actuelles sur le sujet :

  1. N'exposez pas mapStateToProps et mapDispatchToProps si vous n'avez pas besoin de ces mappages dans plusieurs composants. Les exporter juste pour tester est un anti-modèle.
  2. Je vois actuellement les conteneurs comme des collaborateurs (des fonctions qui délèguent d'autres fonctions et ces autres fonctions exécutent la vraie logique).
    Qu'est-ce que ça veut dire?
  3. Les conteneurs n'exécutent aucune logique
  4. La logique de sélection d'un état concerne les fonctions de sélecteur (qui peuvent être purement testées par unité)
  5. La logique des actions est cachée dans les créateurs d'action (ce qui peut dépendre si vous utilisez redux-thunk ou non sont des collaborateurs par eux-mêmes)
  6. La vue rendue est une autre fonction (qui a potentiellement une autre fonction de collaborateur ou rend purement des choses

=> La configuration du test est assez simple lorsque vous utilisez les bibliothèques de test appropriées (je recommande testdouble.js).
=> Maquettez votre sélecteur et vos actions via testdouble.js (en utilisant td.when() )
=> Rendu peu profond de votre composant de conteneur, dive() une fois pour accéder aux méthodes du composant de vue
=> Compte tenu de l'ensemble de règles par td.when() affirmer si le composant se comporte correctement

Pas besoin d'injecter une bibliothèque fakeStore, c'est bien de supprimer le magasin.

Exemple:

/** <strong i="27">@format</strong> */

import React from 'react';
import { shallow } from 'enzyme';

function setup(props) {
  const HasOrganization = require('./HasOrganization').default;
  const defaultProps = {
    store: {
      subscribe: Function.prototype,
      getState: Function.prototype,
      dispatch: Function.prototype,
    },
  };
  const container = shallow(<HasOrganization {...defaultProps} {...props} />);
  return {
    container,
    wrapper: container.dive(),
  };
}

describe('<HasOrganization /> collaborations', () => {
  let getCurrentOrganizationId;
  beforeEach(() => {
    getCurrentOrganizationId = td.replace('../containers/App/userSelectors')
      .selectCurrentOrganizationId;
  });

  test('not render anything when the id is not valid', () => {
    const Dummy = <div />;
    td.when(getCurrentOrganizationId()).thenReturn(null);
    const { wrapper } = setup({ children: Dummy });

    expect(wrapper.find('div').length).toBe(0);
  });

  test('render something when the id is valid', () => {
    const Dummy = <div />;
    td.when(getCurrentOrganizationId()).thenReturn(1);
    const { wrapper } = setup({ children: Dummy });

    expect(wrapper.find('div').length).toBe(1);
  });
});

Les pensées?

@AnaRobynn Tout à fait d'accord avec les points 1 et 2 !

Mes conteneurs sont généralement très simples :

// imports hidden

const mapStateToProps = (state, { userId }) => ({
  isLoading: isLoading(state),
  hasError: hasError(state),
  placeholders: getPlaceholders(userId)(state), // a list of some sort
  shouldDoStuff: shouldDoStuff(state),
});

const mapDispatchToProps = {
  asyncActionPlaceholder,
};

export const mergeProps = (stateProps, dispatchProps, ownProps) => {
  return {
    ...stateProps,
    ...dispatchProps,
    ...ownProps,
    onMount: () => {
      if (stateProps.shouldDoStuff) {
        dispatchProps.asyncActionPlaceholder(ownProps.userId);
      }
    },
  };
};

export default compose(
  connect(
    mapStateToProps,
    mapDispatchToProps,
    mergeProps
  ),
  callOnMount(props => props.onMount())
)(SampleComponent);

Alors je voudrais

  1. espionner tous les sélecteurs et le créateur d'action
  2. test getPlaceholders spy a été appelé avec le bon userId
  3. assert SampleComponent a été rendu avec les accessoires corrects
  4. appelez le prop onMount de SampleComponent et vérifiez qu'il distribue l'action simulée lorsque shouldDoStuff===true
  • Des choses comme le rendu conditionnel de choses en fonction des accessoires que je testerais sur un test séparé pour SampleComponent
  • Je préfère aussi utiliser redux-mock-store pour me moquer du magasin comme configureMockStore([thunk])() mais je pense que c'est bien de ne pas le faire...

TL; DR En gros, seuls les sélecteurs de test ont été appelés avec des arguments corrects, les actions simulées ont été envoyées avec succès (si elles dépendent d'une logique) et que le composant enfant est rendu avec les accessoires corrects

@philihp

  • Je suis très contre les tests de bibliothèques externes, je préfère me moquer de tout et tester les choses de manière isolée.
  • Je suis sûr que les tests de bout en bout détecteront tous les problèmes majeurs causés par les mises à niveau de la bibliothèque.
  • La raison pour laquelle je n'aime pas tester ensemble conteneurs + sélecteurs + créateurs d'action est que vous devrez retester les mêmes sélecteurs et créateurs d'action pour tous les autres conteneurs qui les réutilisent. À ce stade, vous feriez mieux d'ajouter plus de cas de test aux tests unitaires eux-mêmes.

@dougbacelar Pouvez-vous donner un exemple de code de test pour tester le composant que vous avez montré. Vous utilisez une enzyme ? Autre chose? Peu profond? Monter?

@dougbacelar

Je suis très contre les tests de bibliothèques externes, je préfère me moquer de tout et tester les choses de manière isolée.

Faites des simulations quand vous en avez besoin, mais si vous pouvez utiliser le vrai, pourquoi pas ? La moquerie est utile lorsque certains appels sont lents ou ont des effets secondaires comme les requêtes réseau.

La raison pour laquelle je n'aime pas tester ensemble conteneurs + sélecteurs + créateurs d'action est que vous devrez retester les mêmes sélecteurs et créateurs d'action pour tous les autres conteneurs qui les réutilisent. À ce stade, vous feriez mieux d'ajouter plus de cas de test aux tests unitaires eux-mêmes.

Oui , testez vos sélecteurs, créateurs d'actions en toute autonomie. Testez-les soigneusement avec de nombreuses entrées attendues.

Oui , testez également vos conteneurs. Si cela signifie qu'ils offrent une couverture qui se chevauche sur les sélecteurs et les créateurs d'action, ce n'est pas une mauvaise chose.

Voici ce que je veux dire...

import React from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { saveColor } from '../actions/save';
import { selectColors } from '../reducers/colors.js';
import ColorButtons from '../components/ColorButtons';
const ColorButtons = ({ colors, onClick }) => (
  <div>
    {colors.map(color => {
      <button type="button" key={color} onClick={onClick(color)}>{color}</button>
    })}
  </div>
);
ColorButtons.propTypes = {
  colors: PropTypes.arrayOf(PropTypes.string),
  onClick: PropTypes.func.isRequired,
};
const mapStateToProps = state => ({
  colors: selectColors(state.colors),
});
const mapDispatchToProps = dispatch => ({
  onClickColor: color => () => {
    dispatch(saveColor({ color }));
  },
});
export default connect(
  mapStateToProps,
  mapDispatchToProps
)(ColorButtons);
import React from 'react';
import { shallow } from 'enzyme';
import configureStore from 'redux-mock-store';
import ColorButtons from '../ColorButtons';
import { saveColor } from '../../actions/save';
import { selectColors } from '../../reducers/colors.js';
const buildStore = configureStore();
describe('<ColorButtons />', () => {
  let store;
  let wrapper;
  const initialState = { colors: ['red', 'blue'] };
  beforeEach(() => {
    store = buildStore(initialState);
    wrapper = shallow();
  });
  it('passes colors from state', () => {
    expect(wrapper.props().colors).toEqual(selectColors(initialState.colors));
  });
  it('can click yellow', () => {
    const color = 'yellow';
    wrapper.props().onClick(color)();
    expect(store.getActions()).toContainEqual(saveColor({ color }));
  });
});

Ici, cela ne teste pas tous les différents paramètres que selectColors et saveColor pourraient obtenir ; il teste que lorsque <ColorButtons /> est créé, il obtient les propriétés que nous attendons qu'il obtienne. Testez-les absolument dans un autre test.

import { selectColors } from '../colors.js';
describe('selectColors', () => {
  it('returns the colors', state => {
    expect(selectColors(['red'])).toEqual(['red'])
  })
  it('returns an empty array if null', state => {
    expect(selectColors(null)).toEqual([])
  })
  it('returns a flattened array', state => {
    expect(selectColors(['red', ['blue', 'cyan']])).toEqual(['red','blue','cyan'])
  })
})

Je suis très contre les tests de bibliothèques externes, je préfère me moquer de tout et tester les choses de manière isolée.

Faites des simulations quand vous en avez besoin, mais si vous pouvez utiliser le vrai, pourquoi pas ? La moquerie est utile lorsque certains appels sont lents ou ont des effets secondaires comme les requêtes réseau.

Pas d'accord, en partie.
Je ne me moque jamais des bibliothèques externes (ne vous moquez pas de ce que vous ne possédez pas), mais j'écris un wrapper autour par exemple d'axios. Dans mon test de collaboration, je peux simplement simuler toutes les fonctions et m'assurer que tout est correctement câblé.

Dans mes tests de collaboration, je me moque toujours des modules, des fonctions, etc.
Ces fonctions appelées par votre collaborateur peuvent être facilement testées unitairement.

Voici ce que je veux dire...

Aussi en désaccord.
Vous testez implicitement vos réducteurs et sélecteurs ici. Si c'est ce que vous voulez faire, mais je préfère simuler ces fonctions et vérifier simplement si elles sont appelées correctement. Le reste est géré par les réducteurs/sélecteurs.

Ce fil reprend les points soulevés dans https://martinfowler.com/articles/mocksArentStubs.html , Classical and Mockist Testing

D'accord. Verrouillage.

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