2016-07-14 70 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

这会使该功能公开访问,我非常反对。它的当前位置使该功能保持私密,这在测试组件行为时非常重要。 –