Reactjs 如何模拟路由器上下文

Reactjs 如何模拟路由器上下文,reactjs,react-router,enzyme,Reactjs,React Router,Enzyme,我有一个相当简单的react组件(链接包装器,如果路由是活动的,它会添加'active'类): import React,{PropTypes}来自'React'; 从“反应路由器”导入{Link}; 常量导航链接=(道具、上下文)=>{ const isActive=context.router.isActive(props.to,true); const activeClass=isActive?'active':''; 返回( {props.children} ); } NavLink

我有一个相当简单的react组件(链接包装器,如果路由是活动的,它会添加'active'类):

import React,{PropTypes}来自'React';
从“反应路由器”导入{Link};
常量导航链接=(道具、上下文)=>{
const isActive=context.router.isActive(props.to,true);
const activeClass=isActive?'active':'';
返回(
  • {props.children}
  • ); } NavLink.contextTypes={ 路由器:PropTypes.object, }; NavLink.propTypes={ 子项:PropTypes.node, 至:PropTypes.string, }; 导出默认导航链接;
    我应该如何测试它?我唯一的尝试是:

    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);
      });
    });
    
    从“../index”导入导航链接;
    从“expect”导入expect;
    从“酶”导入{mount};
    从“React”导入React;
    描述(“”,()=>{
    它('应该添加活动类',()=>{
    const renderedComponent=mount(,{router:{pathname:'/home'});
    expect(renderedComponent.hasClass('active')).toEqual(true);
    });
    });
    

    它不工作并返回
    TypeError:无法读取未定义的属性“isActive”
    。它确实需要一些路由器模拟,但我不知道如何编写它。

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

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

    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>
        )
      }
    }
    
    import React,{PropTypes}来自“React”
    导出默认类WithContext扩展React.Component{
    静态类型={
    孩子们:任何人,
    上下文:PropTypes.object
    }
    验证儿童(){
    if(this.props.children===未定义){
    抛出新错误('未将任何子组件传递到WithContext')
    }
    如果(this.props.children.length>1){
    抛出新错误('您只能将一个子组件传递到WithContext')
    }
    }
    渲染(){
    类WithContext扩展了React.Component{
    getChildContext(){
    返回this.props.context
    }
    渲染(){
    归还这个。道具。孩子们
    }
    }
    const context=this.props.context
    WithContext.childContextTypes={}
    for(让propertyName在上下文中){
    WithContext.childContextTypes[propertyName]=PropTypes.any
    }
    this.validateChildren()
    返回(
    {this.props.children}
    )
    }
    }
    
    在这里,您可以看到一个示例用法:

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

    它应该会像你期望的那样工作。

    谢谢@Elon Szopos的回答,但我设法写了一些更简单的东西(如下所示):

    从“../index”导入导航链接;
    从“expect”导入expect;
    从“酶”导入{shall};
    从“React”导入React;
    描述(“”,()=>{
    它('应该添加活动类',()=>{
    const context={router:{isActive:(a,b)=>true};
    const renderedComponent=浅(,{context});
    expect(renderedComponent.hasClass('active')).toEqual(true);
    });
    });
    
    我必须将
    mount
    更改为
    shallow
    ,以避免评估
    链接
    ,这会给我一个与react路由器连接的错误
    类型错误:router.createHref不是一个函数

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

    您可以将其用于确切的目的

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

    安装后,您将能够执行以下操作

    describe('my test', () => {
      it('renders', () => {
        const context = createRouterContext()
        const wrapper = shallow(<MyComponent />, { context })
      })
    })
    
    description('我的测试',()=>{
    它('呈现',()=>{
    const context=createRouterContext()
    常量包装器=浅(,{context})
    })
    })
    
    对于react路由器v4,您可以使用
    。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);
    });
    
    从“React”导入React;
    从“道具类型”导入道具类型;
    从“ava”导入测试;
    从“酶”导入{mount};
    从“sinon”进口sinon;
    从“react Router dom”导入{MemoryRouter as Router};
    const mountWithRouter=node=>mount({node});
    测试('直接提交表单',t=>{
    const onSubmit=sinon.spy();
    const wrapper=mountWithRouter();
    const form=wrapper.find('form');
    形式。模拟(“提交”);
    t、 true(提交时调用);
    });
    
    在您需要更新包装组件的道具之前,此操作一直有效。调用
    setProps()
    仅对根组件有效,现在它是
    MemoryRouter
    我想不可能找到一种测试组件的通用方法。但是,在上面的示例中,可以内联设置属性。类似于
    onSubmit
    。要允许在测试组件上使用
    setProps()
    ,可以将props如下传递给它:
    const TestContext=({children,…props})=>{React.cloneElement(children,props)}
    注意“context”周围的大括号进入浅层-我最初错过了这些,我花了一段时间才弄明白为什么它不起作用!“注意:此软件包仅适用于React Router v4。”
    describe('my test', () => {
      it('renders', () => {
        const context = createRouterContext()
        const wrapper = shallow(<MyComponent />, { context })
      })
    })
    
    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);
    });