最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - Enzyme testing an authentication Higher Order Component (HOC) - Stack Overflow

programmeradmin2浏览0评论

I've created a Higher Order Component / Composed Component to ensure a user is authenticated before loading the Component. It's very basic, but I'm having some trouble testing it. I want to test the points below, which are similar to the tests I already have elsewhere:

  • Renders the Component (I normally check by looking for a Component specific className)
  • Has correct props (in my case authenticated)
  • Renders the wrapped Component if authenticated and renders null if not

The 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,
    }

    ponentWillMount() {
      if (!this.props.authenticated) this.context.router.push('/');
    }

    ponentWillUpdate(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);
}

I'm using enzyme and jest for my tests, but haven't found a way of rendering the HOC successfully during my tests.

Any ideas?

Solution thanks to answer below:

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
        posedComponent={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
        posedComponent={MockComponent}
        authenticated={false}
      />,
      { context: { router: { push: jest.fn() } } }
    );

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

I've created a Higher Order Component / Composed Component to ensure a user is authenticated before loading the Component. It's very basic, but I'm having some trouble testing it. I want to test the points below, which are similar to the tests I already have elsewhere:

  • Renders the Component (I normally check by looking for a Component specific className)
  • Has correct props (in my case authenticated)
  • Renders the wrapped Component if authenticated and renders null if not

The 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,
    }

    ponentWillMount() {
      if (!this.props.authenticated) this.context.router.push('/');
    }

    ponentWillUpdate(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);
}

I'm using enzyme and jest for my tests, but haven't found a way of rendering the HOC successfully during my tests.

Any ideas?

Solution thanks to answer below:

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
        posedComponent={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
        posedComponent={MockComponent}
        authenticated={false}
      />,
      { context: { router: { push: jest.fn() } } }
    );

    expect(wrapper.find('MockComponent').length).toEqual(0);
  });
});
Share Improve this question edited Jan 19, 2017 at 9:50 germainelol asked Jan 19, 2017 at 8:08 germainelolgermainelol 3,35115 gold badges48 silver badges83 bronze badges
Add a ment  | 

1 Answer 1

Reset to default 7

The "tricky" part here is that your HOC returns a connected ponent, which makes testing harder because you have shallow render two layers (the connected ponent and the actual ponent) and you have to mock the redux store.

Instead you could define the AuthenticatedComponent upfront and export it as a named export. Than you can test it independently of connect like you test every other ponent:

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

  static propTypes = {
    authenticated: React.PropTypes.bool,
    posedComponent: React.PropTypes.any.isRequired,
  }

  ponentWillMount() {
    if (!this.props.authenticated) this.context.router.push('/');
  }

  ponentWillUpdate(nextProps) {
    if (!nextProps.authenticated) this.context.router.push('/');
  }

  render() {
    const ComposedComponent = this.props.posedComponent;
    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),
      posedComponent: ComposedComponent,
    });
  };

  return connect(mapStateToProps)(AuthenticatedComponent);
}

Example test:

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 ponent', () => {
    expect(wrapper.find('Connect(AuthenticatedComponent)')).toHaveLength(1);
  });
  it('should pass correct props', () => {
    expect(wrapper.find('AuthenticatedComponent').props()).toEqual(
      expect.objectContaining({
        authenticated: false,
        posedComponent: Component,
      })
    );
  });
});

describe('rendering', () => {
  describe('is authenticated', () => {
    const wrapper = shallow(
      <AuthenticatedComponent
        posedComponent={Component}
        authenticated
      />,
      { context: { router: { push: jest.fn() } } }
    );
    it('should render the passed ponent', () => {
      expect(wrapper.find('CustomComponent')).toHaveLength(1);
    });
  });
  describe('is not authenticated', () => {
    const wrapper = shallow(
      <AuthenticatedComponent
        posedComponent={Component}
        authenticated={false}
      />,
      { context: { router: { push: jest.fn() } } }
    );
    it('should not render the passed ponent', () => {
      expect(wrapper.find('CustomComponent')).toHaveLength(0);
    });
  });
});
发布评论

评论列表(0)

  1. 暂无评论