2016-07-14 7 views
2

これは、AirBnBのReactテストライブラリEnzymeを使用して、私のReactコンポーネントのいくつかをリファクタリングしようとしているときに出会った興味深い問題です。Enzymeによるユニットユニットテストは、ヘルパー機能のコンテキストを再バインドしない

私の問題を説明する最善の方法は例であると思います。

test.js:

import React from 'react'; 

function renderInnerSpan() { 
    const {foo} = this.props; 

    if (foo) { 
     return <span>Foo is truthy!</span>; 
    } 

    return <span>Foo is falsy!</span>; 
} 

export default class extends React.Component { 
    render() { 
     return (
      <div> 
       {renderInnerSpan.call(this)} 
      </div> 
     ); 
    }  
} 

そして、ここでは、のためにテストスイートです。ここ

は、その親コン​​ポーネントから受け取る小道具に応じたメッセージを表示する小さなリアクトコンポーネントです2つの通過試験とこの成分:

test.spec.js:

import Test from '../../src/test'; 

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

describe('Test Suite',() => { 
    let renderedElement, 
     expectedProps; 

    function renderComponent() { 
     const componentElement = React.createElement(Test, expectedProps); 

     renderedElement = shallow(componentElement); 
    } 

    beforeEach(() => { 
     expectedProps = { 
      foo: true 
     }; 

     renderComponent(); 
    }); 

    it('should display the correct message for truthy values',() => { 
     const span = renderedElement.props().children; 

     expect(span.props.children).to.equal('Foo is truthy!'); 
    }); 

    it('should display the correct message for falsy values',() => { 
     expectedProps.foo = false; 
     renderComponent(); 

     const span = renderedElement.props().children; 

     expect(span.props.children).to.equal('Foo is falsy!'); 
    }); 
}); 

これは問題なく動作しますが、テストコンポーネントの現在の実装は効率的ではありません。 .call(this)を使用すると、render()関数が呼び出されるたびに新しい関数が作成されます。私はそうのように、コンポーネントのコンストラクタでthisの正しいコンテキストを結合することによってこれを避けることができます:この変更後

export default class extends React.Component { 
    constructor(props) { 
     super(props); 

     renderInnerSpan = renderInnerSpan.bind(this); 
    } 

    render() { 
     return (
      <div> 
       {renderInnerSpan()} 
      </div> 
     ); 
    } 
} 

、コンポーネントはまだ意図したとおりに動作しますが、テストが失敗開始:

AssertionError: expected 'Foo is truthy!' to equal 'Foo is falsy!' 
Expected :Foo is falsy! 
Actual :Foo is truthy! 

私はコンストラクタにconsole.log(props.foo)を追加しました。コンストラクタは、期待したときにコンストラクタが呼び出されていたことを確認し、受け取ったプロップは正しいと確認しました。しかし、console.log(foo)renderInnerSpanの内側に追加しました。foo propを明示的にfalseに設定してコンポーネントを再描画した後でも、値が常に真であるように見えます。

renderInnerSpanは1度だけ結合し、Enzymeはすべての単一のテストでこれを再使用しているようです。だから、何を与える?私は期待した値でコンストラクタを呼び出すテストで自分のコンポーネントを再作成しています - なぜ私のバインドは古い値を使用し続ける関数ですか?

ご協力いただきありがとうございます。

答えて

1

問題は、あなたのテストケースにしようとしているよう機能は、複数回をバインドすることができないことです。

その理由は、コンテキストが単なる関数自体のプロパティではないということです。関数がバインドされると、むしろ関数はbound function exotic objectにラップされます。

エキゾチックオブジェクトの[[BoundThis]]プロパティには、コンテキスト( -assignment)が保存されます。バインドされた関数は、再度バインドされていても、常にこのコンテキストで呼び出されます。


あなたはこの自分自身をテストすることができます。

function foo() { 
 
    console.log(this.bar); 
 
} 
 

 
foo(); // undefined 
 

 
foo = foo.bind({bar: 1}); 
 
foo(); // 1 
 

 
foo = foo.bind({bar: 2}); 
 
foo(); // 1


この問題を解決するために、私はあなたがレンダリング機能と転送からコンテキストに依存関係を削除示唆代わりに関数パラメータを介して必要な入力:

これにより、隠された依存関係が削除され、コードがより読みやすく保守的になります。レンダリング機能を独自のモジュールに移動することを決断したことがあれば、簡単に行うことができます。

あなたはもう、コンストラクタ関数のコンテキストをバインドする必要はありませんので、あなたもあなたstateless functionにコンポーネントに反応変換することができます:

import renderInnerSpan from './renderInnerSpan' 

export default (props) => (
    <div> 
     {renderInnerSpan(props.foo)} 
    </div> 
); 

だから、非常に良く、より読みやすいです! :-)

+0

ステートレス機能コンポーネントは、ここに行く方法のようです。私はやや異なるアプローチを取ってしまい、代わりに 'renderInnerSpan'をSFCに変えました。説明をありがとう、これは今より意味をなさないです。 –

-1

renderInnerSpan関数がクラス外に定義されていて、いくつかの問題が発生する可能性があります。

はこれを試してみてください:

function renderComponent(expectedProps) { 
    const componentElement = React.createElement(Test, expectedProps); 

    return shallow(componentElement); 
} 

そして、あなたは、各テストで小道具を変更している場合は、その理由がない:

import React from 'react'; 


export default class extends React.Component { 
    render() { 
    return (
     <div> 
     {this.renderInnerSpan.bind(this)} 
     </div> 
    ); 
    } 

    renderInnerSpan() { 
    const {foo} = this.props; 

    if (foo) { 
     return <span>Foo is truthy!</span>; 
    } 

    return <span>Foo is falsy!</span>; 
    } 
} 

もう一つは、あなたのrenderComponent機能は次のように書き換えることができることですbeforeEachブロックで小道具を設定します。代わりに各テストで新しいrenderComponentを使用してください。ここ

it('should display the correct message for truthy values',() => { 
    renderedElement = renderComponent({foo: true}); 
    const span = renderedElement.props().children; 

    expect(span.props.children).to.equal('Foo is truthy!'); 
}); 
+0

'renderInnerSpan'がクラスのコンテキスト外で定義できない理由はありません。私が元の投稿で言ったように、元の実装は**動作します**。 –

+0

あなたができるという理由だけで、あなたはすべきではありません。 renderInnerSpanはクラスのコンテキストに属しており、クラス内で定義する必要があります。 – Gennon

+0

これは、私が大いに反対している機能を一般にアクセス可能にするでしょう。現在の場所は関数を非公開にします。これはコンポーネントの動作をテストする際に重要です。 –

関連する問題