【问题标题】:How to mock react-router context如何模拟反应路由器上下文
【发布时间】:2016-11-02 10:50:23
【问题描述】:

我有相当简单的反应组件(如果路由处于活动状态,则添加“活动”类的链接包装器):

import React, { PropTypes } from 'react';
import { Link } from 'react-router';

const NavLink = (props, context) => {
  const isActive = context.router.isActive(props.to, true);
  const activeClass = isActive ? 'active' : '';

  return (
    <li className={activeClass}>
      <Link {...props}>{props.children}</Link>
    </li>
  );
}

NavLink.contextTypes = {
  router: PropTypes.object,
};

NavLink.propTypes = {
  children: PropTypes.node,
  to: PropTypes.string,
};

export default NavLink;

我应该如何测试它?我唯一的尝试是:

import NavLink from '../index';

import expect from 'expect';
import { mount } from 'enzyme';
import React from 'react';

describe('<NavLink />', () => {
  it('should add active class', () => {
    const renderedComponent = mount(<NavLink to="/home" />, { router: { pathname: '/home' } });
    expect(renderedComponent.hasClass('active')).toEqual(true);
  });
});

它不起作用并返回TypeError: Cannot read property 'isActive' of undefined。它肯定需要一些路由器模拟,但我不知道如何编写它。

【问题讨论】:

    标签: reactjs react-router enzyme


    【解决方案1】:

    对于反应路由器 v4,您可以使用 &lt;MemoryRouter&gt;。以 AVA 和酶为例:

    import React from 'react';
    import PropTypes from 'prop-types';
    import test from 'ava';
    import { mount } from 'enzyme';
    import sinon from 'sinon';
    import { MemoryRouter as Router } from 'react-router-dom';
    
    const mountWithRouter = node => mount(<Router>{node}</Router>);
    
    test('submits form directly', t => {
      const onSubmit = sinon.spy();
      const wrapper = mountWithRouter(<LogInForm onSubmit={onSubmit} />);
      const form = wrapper.find('form');
      form.simulate('submit');
    
      t.true(onSubmit.calledOnce);
    });
    

    【讨论】:

    • 这一直有效,直到您需要更新包装组件的道具。调用 setProps() 仅适用于根组件,现在是 MemoryRouter
    • 我想不可能找到一种通用的方法来测试组件。但是,在上面的示例中,可以内联设置属性。喜欢onSubmit
    • 要允许在测试的组件上使用setProps(),可以像这样传递给它:const TestContext = ({children, ...props}) =&gt; &lt;Router&gt;{React.cloneElement(children, props)}&lt;/Router&gt;
    【解决方案2】:

    您可以将https://github.com/pshrmn/react-router-test-context 用于该目的

    “创建一个复制 React Router 的 context.router 结构的伪上下文对象。这对于使用 Enzyme 进行浅层单元测试很有用。”

    安装后,您将能够执行类似的操作

    describe('my test', () => {
      it('renders', () => {
        const context = createRouterContext()
        const wrapper = shallow(<MyComponent />, { context })
      })
    })
    

    【讨论】:

    • "注意:此包仅适用于 React Router v4。"
    【解决方案3】:

    感谢@Elon Szopos 的回答,但我设法写了一些更简单的东西(遵循https://github.com/airbnb/enzyme/pull/62):

    import NavLink from '../index';
    
    import expect from 'expect';
    import { shallow } from 'enzyme';
    import React from 'react';
    
    describe('<NavLink />', () => {
      it('should add active class', () => {
        const context = { router: { isActive: (a, b) => true } };
        const renderedComponent = shallow(<NavLink to="/home" />, { context });
        expect(renderedComponent.hasClass('active')).toEqual(true);
      });
    });
    

    我必须将mount 更改为shallow,以免评估Link,这给了我与react-router TypeError: router.createHref is not a function 相关的错误。

    我宁愿拥有“真正的”反应路由器,而不仅仅是一个对象,但我不知道如何创建它。

    【讨论】:

    • 注意“上下文”周围的大括号被传递到浅层 - 我最初错过了这些,我花了一段时间才弄清楚为什么它不起作用!
    【解决方案4】:

    测试依赖于上下文的组件可能有点棘手。我所做的是编写一个我在测试中使用的包装器。

    您可以在下面找到包装器:

    import React, { PropTypes } from 'react'
    
    export default class WithContext extends React.Component {
      static propTypes = {
        children: PropTypes.any,
        context: PropTypes.object
      }
    
      validateChildren () {
        if (this.props.children === undefined) {
          throw new Error('No child components were passed into WithContext')
        }
        if (this.props.children.length > 1) {
          throw new Error('You can only pass one child component into WithContext')
        }
      }
    
      render () {
        class WithContext extends React.Component {
          getChildContext () {
            return this.props.context
          }
    
          render () {
            return this.props.children
          }
        }
    
        const context = this.props.context
    
        WithContext.childContextTypes = {}
    
        for (let propertyName in context) {
          WithContext.childContextTypes[propertyName] = PropTypes.any
        }
    
        this.validateChildren()
    
        return (
          <WithContext context={this.props.context}>
            {this.props.children}
          </WithContext>
        )
      }
    }
    

    在这里你可以看到一个示例用法:

      <WithContext context={{ location: {pathname: '/Michael/Jackson/lives' }}}>
        <MoonwalkComponent />
      </WithContext>
    
      <WithContext context={{ router: { isActive: true }}}>
        <YourTestComponent />
      </WithContext>
    

    它应该可以正常工作。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2017-12-21
      • 2019-12-25
      • 2019-06-02
      • 2018-10-13
      • 2021-12-13
      • 2021-08-24
      • 2019-06-14
      • 1970-01-01
      相关资源
      最近更新 更多