web-dev-qa-db-ja.com

装飾をテストする方法React浅いレンダリングのコンポーネント

私はこのチュートリアルに従っています: http://reactkungfu.com/2015/07/approaches-to-testing-react-components-an-overview/

「浅いレンダリング」がどのように機能するかを学習しようとしています。

より高次のコンポーネントがあります:

_import React from 'react';

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

そしてコンポーネント:

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

そしてテスト:

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

result変数はthis.renderer.getRenderOutput()を保持します

チュートリアルでは、_result.type_は次のようにテストされます。

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

私の場合、resultをログに記録すると、次のようになります。

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

だから私は私のテストを次のように変更しました:

expect(result.type).toEqual(PlayerProfile)

今それは私にこのエラーを与えます:

_Assertion Error: expected [Function: PlayerProfile] to equal [Function: withMUI]_

したがって、PlayerProfileの型は高次関数withMUIです。

PlayerProfileで装飾されたwithMUI、浅いレンダリングを使用して、PlayerProfileコンポーネントのみがレンダリングされ、その子はレンダリングされません。したがって、浅いレンダリングは、私が想定する装飾されたコンポーネントでは機能しません。

私の質問は:

なぜチュートリアルでは_result.type_がdivであることが期待されていますが、私の場合はそうではありません。

浅いレンダリングを使用して、より高次のコンポーネントで装飾されたReactコンポーネントをテストするにはどうすればよいですか?

17
eguneys

できません。最初に、デコレータを少し脱糖しましょう。

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

withMUIは別のクラスを返すため、PlayerProfileクラスはwithMUIのクロージャ内にのみ存在します。

これは簡単なバージョンです。

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

あなたは関数に値を渡します、それはそれを返しません、あなたは値を持っていません。

ソリューション?それへの参照を保持します。

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

次に、コンポーネントのラップされたバージョンとラップされていないバージョンの両方をエクスポートできます。

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

このコンポーネントを使用する通常のコードは変更されませんが、テストではこれを使用します。

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

別の方法は、withMUI関数を(x) => x(識別関数)にモックすることです。これは奇妙な副作用を引き起こす可能性があり、テスト側から行う必要があるため、デコレータが追加されると、テストとソースが同期しなくなる可能性があります。

ここでは、デコレータを使用しないことが安全なオプションのようです。

20
Brigand

Enzymeを使用して、dive()と呼ばれるメソッドでShallowで高次/デコレーターをテストする

このリンクをたどって、ダイビングの仕組みを確認してください

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

したがって、高次のコンポーネントを浅くしてから、内部に潜ることができます。

上記の例では:

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

同様に、プロパティにアクセスしてテストすることができます。

11
Kamaraju

'babel-plugin-remove-decorators'プラグインを使用できます。このソリューションでは、装飾されたコンポーネントと装飾されていないコンポーネントをエクスポートせずに、コンポーネントを通常どおりに作成できます。

最初にプラグインをインストールしてから、次の内容のファイルを作成します。「babelTestingHook.js」と呼びます。

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

以下のようにテストを実行すると、デコレータが無視され、コンポーネントを通常どおりテストできます

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

上記の例は、decoratorの概念が「高次コンポーネント」の概念と交換可能に使用されているため、混乱を招くと思います。私は一般的にそれらを組み合わせて使用​​し、テスト/再配線/モッキングを容易にします。

私はデコレーターを使用します:

  • 子コンポーネントに小道具を提供します。通常、フラックスストアにバインド/リッスンします

より高次のコンポーネントを使用する場所

  • より宣言的な方法でコンテキストをバインドする

再配線の問題は、デコレーターの場合のように、エクスポートされた関数/クラスの外部で適用されるものを再配線できないと思います。

デコレータと高次コンポーネントの組み合わせを使用したい場合は、次のようなことができます。

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

その後、テストしたい場合は、デコレーターがエクスポートされた高次コンポーネントの内部にあるため、デコレーターから提供されたストアを簡単に「再配線」できます。

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

});

これで元の質問に実際に答えられないと確信していますが、デコレータと高次コンポーネントを使用する場合の調整に問題があると思います。

いくつかの良いリソースはここにあります:

1
dtothefp

私の場合、デコレータは非常に便利であり、アプリケーションでそれらを削除したくない(またはラップされたバージョンとラップされていないバージョンを返す)必要はありません。

私の考えでは、これを行う最良の方法は、babel-plugin-remove-decorators(テストでそれらを削除するために使用できます)を使用することです。

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

babel.transform要素を配列値として渡すbabel-plugin-remove-decorators:before呼び出しに注意してください。以下を参照してください。 https://babeljs.io/docs/usage/options/

これをJest(私が使用したもの)に接続するには、package.jsonで以下のような設定を行うことができます。

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

ここで、preprocessor.jsはプリプロセッサの名前です。

0
Marty