我创建了一个高阶组件/组合组件,以确保在加载组件之前对用户进行身份验证.这是非常基本的,但我在测试它时遇到了一些麻烦.我想测试下面的几点,这与我在其他地方已有的测试类似:
>渲染组件(我通常通过查找特定于组件的className来检查)
>有正确的道具(在我的情况下验证)
>如果经过身份验证,则呈现包装的Component,否则呈现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); }
我正在使用酶和jest进行测试,但是在我的测试中没有找到成功渲染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); }); });
解决方法
这里的“棘手”部分是你的HOC返回一个连接组件,这使得测试更难,因为你有两层浅层渲染(连接组件和实际组件),你必须模拟redux存储.
相反,您可以预先定义AuthenticatedComponent并将其导出为命名导出.您可以像测试其他所有组件一样独立于连接测试它:
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); }); }); });