web-dev-qa-db-fra.com

Comment tester le composant React décoré avec un rendu peu profond

Je suis ce tutoriel: http://reactkungfu.com/2015/07/approaches-to-testing-react-components-an-overview/

Essayer d'apprendre comment le "rendu superficiel" fonctionne.

J'ai un composant d'ordre supérieur:

import React from 'react';

function withMUI(ComposedComponent) {
  return class withMUI {
    render() {
      return <ComposedComponent {...this.props}/>;
    }
  };
}

et un composant:

@withMUI
class PlayerProfile extends React.Component {
  render() {
    const { name, avatar } = this.props;
    return (
      <div className="player-profile">
        <div className='profile-name'>{name}</div>
        <div>
          <Avatar src={avatar}/>
        </div>
      </div>
    );
  }
}

et un test:

describe('PlayerProfile component - testing with shallow rendering', () => {
  beforeEach(function() {
   let {TestUtils} = React.addons;

    this.TestUtils = TestUtils;

    this.renderer = TestUtils.createRenderer();
    this.renderer.render(<PlayerProfile name='user'
                                            avatar='avatar'/>);
  });

  it('renders an Avatar', function() {
    let result = this.renderer.getRenderOutput();
    console.log(result);
    expect(result.type).to.equal(PlayerProfile);
  });
});

La variable result contient this.renderer.getRenderOutput()

Dans le tutoriel, le result.type est testé comme suit:

expect(result.type).toEqual('div');

dans mon cas, si je me connecte le result c'est:

LOG: Object{type: function PlayerProfile() {..}, .. }

alors j'ai changé mon test comme:

expect(result.type).toEqual(PlayerProfile)

maintenant cela me donne cette erreur:

Assertion Error: expected [Function: PlayerProfile] to equal [Function: withMUI]

Donc, le type de PlayerProfile est la fonction d'ordre supérieur withMUI.

PlayerProfile décoré avec withMUI, avec un rendu peu profond, seul le composant PlayerProfile est rendu et non ses enfants. Un rendu aussi superficiel ne fonctionnerait pas avec des composants décorés, je suppose.

Ma question est:

Pourquoi, dans le tutoriel, result.type devrait être un div, mais dans mon cas, ce n’est pas le cas.

Comment puis-je tester un composant React décoré avec un composant d'ordre supérieur en utilisant un rendu peu profond?

15
eguneys

Tu ne peux pas. D'abord, décontrons légèrement le décorateur:

let PlayerProfile = withMUI(
    class PlayerProfile extends React.Component {
      // ...
    }
);

withMUI renvoie une classe différente, de sorte que la classe PlayerProfile n'existe que dans la fermeture de withMUI.

Voici une version simplifiée:

var withMUI = function(arg){ return null };
var PlayerProfile = withMUI({functionIWantToTest: ...});

Vous transmettez la valeur à la fonction, elle ne la restitue pas, vous n'avez pas la valeur.

La solution? Tenez une référence à cela.

// no decorator here
class PlayerProfile extends React.Component {
  // ...
}

Ensuite, nous pouvons exporter les versions encapsulées et non encapsulées du composant:

// this must be after the class is declared, unfortunately
export default withMUI(PlayerProfile);
export let undecorated = PlayerProfile;

Le code normal utilisant ce composant ne change pas, mais vos tests utiliseront ceci:

import {undecorated as PlayerProfile} from '../src/PlayerProfile';

L'alternative consiste à simuler la fonction withMUI comme étant (x) => x (la fonction d'identité). Cela peut entraîner des effets secondaires étranges et doit être effectué du côté des tests. Par conséquent, vos tests et votre source risquent de ne plus être synchronisés à mesure que des décorateurs sont ajoutés.

Ne pas utiliser de décorateurs semble être l'option sûre ici.

19
FakeRainBrigand

Utilisez Enzyme pour tester les décorateurs supérieurs avec Shallow Avec une méthode appelée dive ()

Suivez ce lien pour voir comment fonctionne la plongée 

https://github.com/airbnb/enzyme/blob/master/docs/api/ShallowWrapper/dive.md

Vous pouvez donc plonger le composant dans un ordre peu élevé, puis plonger à l'intérieur.

Dans l'exemple ci-dessus:

const wrapper=shallow(<PlayerProfile name={name} avatar={}/>)
expect(wrapper.find("PlayerProfile").dive().find(".player-profile").length).toBe(1)

De même, vous pouvez accéder aux propriétés et les tester.

10
Kamaraju

Vous pouvez utiliser le plugin 'babel-plugin-remove-decorators'. Cette solution vous permettra d'écrire normalement vos composants sans exporter les composants décorés et non décorés.

Installez d'abord le plug-in, puis créez un fichier avec le contenu suivant, appelons-le 'babelTestingHook.js'

require('babel/register')({
 'stage': 2,
 'optional': [
  'es7.classProperties',
  'es7.decorators',
  // or Whatever configs you have
  .....
],
'plugins': ['babel-plugin-remove-decorators:before']
});

et exécuter vos tests comme ci-dessous ignorera les décorateurs et vous pourrez tester les composants normalement

mocha ./tests/**/*.spec.js --require ./babelTestingHook.js --recursive
3
Qusai Jouda

Je pense que l'exemple ci-dessus est source de confusion car le concept decorator est utilisé de manière interchangeable avec l'idée d'un "composant d'ordre supérieur". Je les utilise généralement en combinaison, ce qui facilite les tests/recâblage/moquages. 

J'utiliserais décorateur pour:

  • Fournit des accessoires à un composant enfant, généralement pour lier/écouter un magasin de flux

Où comme j'utiliserais un composant d'ordre supérieur

  • pour lier le contexte d'une manière plus déclarative

Le problème avec le recâblage est que je ne pense pas que vous puissiez recâbler quoi que ce soit qui est appliqué en dehors de la fonction/classe exportée, comme dans le cas d'un décorateur.

Si vous souhaitez utiliser une combinaison de décorateurs et de composants d'ordre supérieur, vous pouvez procéder comme suit:

//withMui-decorator.jsx
function withMUI(ComposedComponent) {
  return class withMUI extends Component {
    constructor(props) {
      super(props);
      this.state = {
        store1: ///bind here based on some getter
      };
    }
    render() {
      return <ComposedComponent {...this.props} {...this.state} {...this.context} />;
    }
  };
}

//higher-order.jsx
export default function(ChildComp) {

  @withMui  //provide store bindings
  return class HOC extends Component {
    static childContextTypes = {
      getAvatar: PropTypes.func
    };

    getChildContext() {
      let {store1} = this.props;

      return {
        getAvatar: (id) => ({ avatar: store1[id] });
      };
    }
  }
}

//child.js
export default Child extends Component {
  static contextTypes = {
    getAvatar: PropTypes.func.isRequired
  };
  handleClick(id, e) {
    let {getAvatar} = this.context;

    getAvatar(`user_${id}`);
  }
  render() {
    let buttons = [1,2,3].map((id) => {
      return <button type="text" onClick={this.handleClick.bind(this, id)}>Click Me</button>
    });

    return <div>{buttons}</div>;  
  }
}

//index.jsx
import HOC from './higher-order';
import Child from './child';

let MyComponent = HOC(Child);
React.render(<MyComponent {...anyProps} />, document.body);

Ensuite, lorsque vous souhaitez tester, vous pouvez facilement "recâbler" vos magasins fournis par le décorateur, car celui-ci se trouve à l'intérieur du composant de commande supérieure exporté.

//spec.js
import HOC from 'higher-order-component';
import Child from 'child';

describe('rewire the state', () => {
  let mockedMuiDecorator = function withMUI(ComposedComponent) {
    return class withMUI extends Component {
      constructor(props) {
        super(props);
        this.state = {
          store1: ///mock that state here to be passed as props
        };
      }
      render()  {
        //....
      }
    }
  }

  HOC.__Rewire__('withMui', mockedMuiDecorator);
  let MyComponent = HOC(Child);

  let child = TestUtils.renderIntoDocument(
    <MyComponent {...mockedProps} />
  );

  let childElem = React.findDOMNode(child);
  let buttons = childElem.querySelectorAll('button');

  it('Should render 3 buttons', () => {
    expect(buttons.length).to.equal(3);
   });

});

Je suis à peu près sûr que cela ne répond pas vraiment à votre question initiale, mais je pense que vous avez du mal à concilier le moment d'utilisation des décorateurs par rapport aux composants d'ordre supérieur.

quelques bonnes ressources sont ici:

1
dtothefp

Dans mon cas, les décorateurs sont très utiles et je ne veux pas m'en débarrasser (ni renvoyer les versions emballées et non emballées) dans mon application. 

La meilleure façon de faire cela, à mon avis, est d'utiliser le babel-plugin-remove-decorators (qui peut être utilisé pour les supprimer dans les tests), a dit Qusai, mais j'ai écrit le pré-processeur différemment, comme ci-dessous:

'use strict';

var babel = require('babel-core');

module.exports = {
  process: function(src, filename) {
    // Ignore files other than .js, .es, .jsx or .es6
    if (!babel.canCompile(filename)) {
     return '';
    }

    if (filename.indexOf('node_modules') === -1) {
      return babel.transform(src, {
        filename: filename, 
        plugins: ['babel-plugin-remove-decorators:before']
      }).code;
    }

    return src;
  }
};

Prenez note de l'appel babel.transform qui im ​​passage l'élément babel-plugin-remove-decorators:before en tant que valeur de tableau, voir: https://babeljs.io/docs/usage/options/

Pour raccorder cela avec Jest (ce que j’ai utilisé), vous pouvez le faire avec les paramètres comme ci-dessous dans votre package.json:

"jest": {
  "rootDir": "./src",
  "scriptPreprocessor": "../preprocessor.js",
  "unmockedModulePathPatterns": [
    "fbjs",
    "react"
  ]
},

preprocessor.js est le nom du préprocesseur.

0
Marty