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

时间:2017-01-19 08:08:06

标签: javascript reactjs redux react-redux enzyme

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

  • 呈现组件(我通常通过查找特定于组件的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 个答案:

答案 0 :(得分:7)

&#34;棘手&#34;这里的部分是你的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);
    });
  });
});