酶测试认证高阶组件(HOC)

Posted

技术标签:

【中文标题】酶测试认证高阶组件(HOC)【英文标题】:Enzyme testing an authentication Higher Order Component (HOC) 【发布时间】:2017-06-03 20:31:39 【问题描述】:

我创建了一个高阶组件/组合组件,以确保在加载组件之前对用户进行身份验证。这是非常基本的,但我在测试它时遇到了一些麻烦。我想测试以下几点,这与我在其他地方已经进行的测试类似:

呈现组件(我通常通过查找特定于组件的className 来检查) 有正确的props(在我的情况下是authenticated) 如果是authenticated,则呈现包装的组件,如果不是,则呈现null

HOC:

import React from 'react';
import  connect  from 'react-redux';
import  createStructuredSelector  from 'reselect';

import  makeSelectAuthenticated  from 'containers/App/selectors';

export default function RequireAuth(ComposedComponent) 
  class AuthenticatedComponent extends React.Component 
    static contextTypes = 
      router: React.PropTypes.object,
    

    static propTypes = 
      authenticated: React.PropTypes.bool,
    

    componentWillMount() 
      if (!this.props.authenticated) this.context.router.push('/');
    

    componentWillUpdate(nextProps) 
      if (!nextProps.authenticated) this.context.router.push('/');
    

    render() 
      return (
        <div className="authenticated">
           this.props.authenticated ? <ComposedComponent ...this.props /> : null 
        </div>
      );
    
  

  const mapStateToProps = createStructuredSelector(
    authenticated: makeSelectAuthenticated(),
  );

  return connect(mapStateToProps)(AuthenticatedComponent);

我正在使用 enzymejest 进行测试,但在我的测试期间没有找到成功渲染 HOC 的方法。

有什么想法吗?

感谢以下回答:

import React from 'react';
import  shallow, mount  from 'enzyme';
import  Provider  from 'react-redux';

import  AuthenticatedComponent  from '../index';

describe('AuthenticatedComponent', () => 
  let MockComponent;

  beforeEach(() => 
    MockComponent = () => <div />;
    MockComponent.displayName = 'MockComponent';
  );

  it('renders its children when authenticated', () => 
    const wrapper = shallow(
      <AuthenticatedComponent
        composedComponent=MockComponent
        authenticated=true
      />,
       context:  router:  push: jest.fn()   
    );

    expect(wrapper.find('MockComponent').length).toEqual(1);
  );

  it('renders null when not authenticated', () => 
    const wrapper = shallow(
      <AuthenticatedComponent
        composedComponent=MockComponent
        authenticated=false
      />,
       context:  router:  push: jest.fn()   
    );

    expect(wrapper.find('MockComponent').length).toEqual(0);
  );
);

【问题讨论】:

【参考方案1】:

这里的“棘手”部分是您的 HOC 返回一个连接组件,这使得测试变得更加困难,因为您有两个浅层渲染(连接组件和实际组件)并且您必须模拟 redux 存储。

相反,您可以预先定义 AuthenticatedComponent 并将其导出为命名导出。您可以像测试其他所有组件一样独立于connect 对其进行测试:

export class AuthenticatedComponent extends React.Component 
  static contextTypes = 
    router: React.PropTypes.object,
  

  static propTypes = 
    authenticated: React.PropTypes.bool,
    composedComponent: React.PropTypes.any.isRequired,
  

  componentWillMount() 
    if (!this.props.authenticated) this.context.router.push('/');
  

  componentWillUpdate(nextProps) 
    if (!nextProps.authenticated) this.context.router.push('/');
  

  render() 
    const ComposedComponent = this.props.composedComponent;
    return (
      <div className="authenticated">
         this.props.authenticated ? <ComposedComponent ...this.props /> : null 
      </div>
    );
  


export default function RequireAuth(ComposedComponent) 
  const mapStateToProps = () => 
    const selectIsAuthenticated = makeSelectAuthenticated();
    return (state) => (
      authenticated: selectIsAuthenticated(state),
      composedComponent: ComposedComponent,
    );
  ;

  return connect(mapStateToProps)(AuthenticatedComponent);

示例测试:

import React from 'react';
import  shallow, mount  from 'enzyme';
import  Provider  from 'react-redux';
import configureStore from 'redux-mock-store';
import RequireAuth,  AuthenticatedComponent  from '../';

const Component = () => <div />;
Component.displayName = 'CustomComponent';

const mockStore = configureStore([]);

describe.only('HOC', () => 
  const RequireAuthComponent = RequireAuth(Component);
  const context =  router:  push: jest.fn()  ;
  const wrapper = mount(
    <Provider store=mockStore()>
      <RequireAuthComponent />
    </Provider>,
    
      context,
      childContextTypes:  router: React.PropTypes.object.isRequired ,
    
  );
  it('should return a component', () => 
    expect(wrapper.find('Connect(AuthenticatedComponent)')).toHaveLength(1);
  );
  it('should pass correct props', () => 
    expect(wrapper.find('AuthenticatedComponent').props()).toEqual(
      expect.objectContaining(
        authenticated: false,
        composedComponent: Component,
      )
    );
  );
);

describe('rendering', () => 
  describe('is authenticated', () => 
    const wrapper = shallow(
      <AuthenticatedComponent
        composedComponent=Component
        authenticated
      />,
       context:  router:  push: jest.fn()   
    );
    it('should render the passed component', () => 
      expect(wrapper.find('CustomComponent')).toHaveLength(1);
    );
  );
  describe('is not authenticated', () => 
    const wrapper = shallow(
      <AuthenticatedComponent
        composedComponent=Component
        authenticated=false
      />,
       context:  router:  push: jest.fn()   
    );
    it('should not render the passed component', () => 
      expect(wrapper.find('CustomComponent')).toHaveLength(0);
    );
  );
);

【讨论】:

感谢您的解释,您可以举例说明如何在测试中仅渲染组件吗?我使用shallowmount 尝试了很多不同的方法,但似乎无法正确渲染任何东西以进行测试。 另外,您传递给mapStateToPropscomposedComponent 变量有点困惑。那仍然会正常运行吗? 添加了如何测试AuthenticatedComponent 的示例。我同意使用 mapStateToProps 传递 composedComponent 看起来有点奇怪。由于 AuthenticatedComponent 现在是在 HOC 之外定义的,因此我们可以将 ComposedComponent 传递给它的唯一方法是通过 props。我想我们可以使用mapStateToProps 或这个。 太棒了,我会将我的最终测试文件发布到 OP 以便您检查它,如果能得到您的反馈会很好。将此标记为正确答案 我在您的回答中发现了一个问题,当我尝试加载我使用的包装组件时,我使用了export default RequireAuth(SomeComponent); ...但我收到了The prop composedComponent is marked as required in AuthenticatedComponent, but its value is undefined。很奇怪,因为实际上我在上面发布的测试都通过了。

以上是关于酶测试认证高阶组件(HOC)的主要内容,如果未能解决你的问题,请参考以下文章

React HOC(高阶组件)

HOC — react高阶组件

react-高阶组件-Hoc

高阶函数HOF和高阶组件HOC(Higher Order Func/Comp)

高阶函数HOF和高阶组件HOC(Higher Order Func/Comp)

[react] 使用高阶组件(HOC)实现一个loading组件