如何模拟反应路由器上下文
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,您可以使用 <MemoryRouter>
。以 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) => <Router>React.cloneElement(children, props)</Router>
【参考方案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>
它应该可以正常工作。
【讨论】:
以上是关于如何模拟反应路由器上下文的主要内容,如果未能解决你的问题,请参考以下文章
反应警告:失败的上下文类型:所需的上下文“路由器”未在“组件”中指定