2016-07-14 57 views
2

這是我碰到的,而試圖通過Airbnb的陣營測試庫,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> 
     ); 
    }  
} 

,這裏是一個測試套件此組件與兩個通過測試:

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裏面,它看起來像值爲true,則所有的時間,即使經過重新呈現組件,其foo道具明確設置爲false

它看起來像renderInnerSpan是隻能綁定一次,酶重新使用這種爲每一個測試。那麼,是什麼給了?我正在測試中重新創建我的組件,它使用我期望的值調用它的構造函數 - 爲什麼我的約束函數renderInnerSpan繼續使用舊值?

在此先感謝您的幫助。

回答

1

的這裏的問題是,功能不能綁定多次,因爲你是在你的測試用例努力。

原因是上下文不僅僅是函數本身的屬性。當一個函數被綁定時,它被封裝在一個bound function exotic object中。

上下文(this -assignment)保存在異國情調對象的[[BoundThis]]屬性中。綁定函數將始終在此上下文中調用,即使它再次綁定。


您可以測試這個自己:

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

 
foo(); // undefined 
 

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

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


爲了解決這個問題,我建議你從渲染功能和轉讓除去依賴於上下文所有需要通過功能參數輸入:

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

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

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

這可以消除隱藏的依賴關係,使代碼更具可讀性和可維護性。如果您決定將渲染功能移至其自己的模塊,現在可以輕鬆完成。

既然你不需要在構造函數上下文綁定了,你甚至可以改變你的陣營組件到stateless function

import renderInnerSpan from './renderInnerSpan' 

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

所以更漂亮,更具可讀性! :-)

+0

無狀態功能組件看起來像是要走到這裏的路。我最終採取了一種稍微不同的方法,並將我的'renderInnerSpan'變成了SFC。感謝您的解釋,現在這變得更有意義。 –

-1

在我看來,你定義了renderInnerSpan函數以外的類,這可能會產生一些問題。

試試這個:

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功能可以改寫這樣的:

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

    return shallow(componentElement); 
} 

如果你正在改變道具在每個測試,則沒有理由在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

這會使該功能公開訪問,我非常反對。它的當前位置使該功能保持私密,這在測試組件行爲時非常重要。 –