如何模拟反应路由器上下文

Posted

技术标签:

【中文标题】如何模拟反应路由器上下文【英文标题】: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。它肯定需要一些路由器模拟,但我不知道如何编写它。

【问题讨论】:

【参考方案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>

它应该可以正常工作。

【讨论】:

以上是关于如何模拟反应路由器上下文的主要内容,如果未能解决你的问题,请参考以下文章

如何将上下文 api 与反应路由器 v4 一起使用?

反应警告:失败的上下文类型:所需的上下文“路由器”未在“组件”中指定

反应路由器dom v4 |上下文路由器和路由未定义

使用身份验证和用户上下文反应路由

未捕获的错误:在路由器上下文之外呈现的 <Link> 无法导航。(…)反应

检测反应路由器中的先前路径?