【问题标题】:Jest, Enzyme: Invariant Violation: You should not use <Route> or withRouter() outside a <Router>开玩笑,酶:不变违规:您不应该在 <Router> 之外使用 <Route> 或 withRouter()
【发布时间】:2019-11-27 11:58:40
【问题描述】:

我有一个&lt;UserListComponent /&gt;,它输出一个&lt;Contact /&gt; 组件和&lt;Contacts /&gt; 提供的联系人列表。

问题是在测试&lt;UserListComponent /&gt;时,当我尝试挂载它时,测试输出错误Invariant Violation: You should not use &lt;Route&gt; or withRouter() outside a &lt;Router&gt;

withRouter() 用于&lt;Contacts /&gt; 组件中。

如何在没有路由器的情况下模拟ContactsComponent 来测试父组件?

我发现了一些类似的问题https://www.bountysource.com/issues/49297944-invariant-violation-you-should-not-use-route-or-withrouter-outside-a-router 但它只描述组件被withRouter() 本身覆盖的情况,而不是子组件。

UserList.test.jsx

const mockResp = {
  count: 2,
  items: [
    {
      _id: 1,
      name: 'User1',
      email: 'email1@gmail.com',
      phone: '+123456',
      online: false
    },
    {
      _id: 2,
      name: 'User2',
      email: 'email2@gmail.com',
      phone: '+789123',
      online: false
    },
    {
      _id: 3,
      name: 'User3',
      email: 'email3@gmail.com',
      phone: '+258369147',
      online: false
    }
  ],
  next: null
}

describe('UserList', () => {
  beforeEach(() => {
    fetch.resetMocks()
  });

  test('should output list of users', () => {
    fetch.mockResponseOnce(JSON.stringify(mockResp));

    const wrapper = mount(<UserListComponent user={mockResp.items[2]} />);

    expect(wrapper.find('.contact_small')).to.have.length(3);
  });

})

UserList.jsx

export class UserListComponent extends PureComponent {
  render() {
    const { users, error } = this.state;
    return (
      <React.Fragment>
        <Contact
          userName={this.props.user.name}
          content={this.props.user.phone}
        />
        {error ? <p>{error.message}</p> : <Contacts type="contactList" user={this.props.user} contacts={users} />}
      </React.Fragment>
    );
  }
}

Contacts.jsx

class ContactsComponent extends Component {
  constructor() {
    super();
    this.state = {
      error: null,
    };
  }

  render() {
    return (
      <React.Fragment>
        <SectionTitle title="Contacts" />
        <div className="contacts">
         //contacts
        </div>
      </React.Fragment>
    );
  }
}

export const Contacts = withRouter(ContactsComponent);

【问题讨论】:

    标签: reactjs react-router jestjs enzyme react-router-dom


    【解决方案1】:

    使用上下文包裹 Mount 的实用函数

    在测试中使用路由器包装挂载是可行的,但在某些情况下,您不希望路由器成为挂载中的父组件。因此,我目前正在使用包装函数将上下文注入到 mount 中:

    import { BrowserRouter } from 'react-router-dom';
    import Enzyme, { shallow, mount } from 'enzyme';
    
    import { shape } from 'prop-types';
    
    // Instantiate router context
    const router = {
      history: new BrowserRouter().history,
      route: {
        location: {},
        match: {},
      },
    };
    
    const createContext = () => ({
      context: { router },
      childContextTypes: { router: shape({}) },
    });
    
    export function mountWrap(node) {
      return mount(node, createContext());
    }
    
    export function shallowWrap(node) {
      return shallow(node, createContext());
    }
    

    这可能在一个名为 contextWrap.js 的文件中,位于测试助手目录中。

    示例描述块:

    import React from 'react';
    import { TableC } from '../../src/tablec';
    import { mountWrap, shallowWrap } from '../testhelp/contextWrap';
    import { expectedProps } from './mockdata'
    
    describe('Table', () => {
      let props;
      let component;
      const wrappedShallow = () => shallowWrap(<TableC {...props} />);
    
      const wrappedMount = () => mountWrap(<TableC {...props} />);
    
      beforeEach(() => {
        props = {
          query: {
            data: tableData,
            refetch: jest.fn(),
          },
        };
        if (component) component.unmount();
      });
    
      test('should render with mock data in snapshot', () => {
        const wrapper = wrappedShallow();
        expect(wrapper).toMatchSnapshot();
      });
    
      test('should call a DeepTable with correct props', () => {
        const wrapper = wrappedMount();
        expect(wrapper.find('DeepTable').props()).toEqual(expectedProps);
      });
    
    });
    

    您还可以使用此模式将子组件包装在其他类型的上下文中,例如,如果您使用 react-intl、material-ui 或您自己的上下文类型。

    【讨论】:

    • 第一个文件中的'mount'函数来自哪里?
    • 这是酶的挂载 - 我相信我在调用执行 import Enzyme, { shallow, mount } from 'enzyme'; 并设置 global.mount = mount; 的测试之前使用 jestSetup 文件全局设置它。添加以澄清答案,非常感谢。
    • 这应该是公认的答案。我认为它比使用接受的答案方法要干净得多。
    • @BenLime 我同意,此解决方案允许使用wrapper.setPropswrapper.statewrapper.setState,无需进一步修改。
    • 救命稻草。谢谢!
    【解决方案2】:

    要测试包含 &lt;Route&gt;withRouter 的组件(使用 Jest),您需要在测试中导入 Router,而不是在组件中

    import { BrowserRouter as Router } from 'react-router-dom';
    

    并像这样使用它

    app = shallow(
        <Router>
            <App />
        </Router>);
    

    【讨论】:

    • 不变违规:在“Connect(Header)”的上下文或道具中找不到“store”。要么将根组件包装在 中,要么将“store”作为道具显式传递给“Connect(Header)”。
    • @AnupamMaurya 使用MemoryRouter 而不是BrowserRouter,它会起作用。
    • 我遇到了与@AnupamMaurya 相同的错误,我使用了memory router
    • 这真的适用于shallow吗?我觉得mount需要用到
    【解决方案3】:

    您需要用BrowserRouter 或等价物包裹App, 请参阅下面的简单测试用例示例,这是一个使用 React Router 的组件应用程序

    import React from "react";
    import ReactDOM from "react-dom";
    import { BrowserRouter } from "react-router-dom";
    import App from "./App";
    
    it("renders without crashing", () => {
      const div = document.createElement("div");
      ReactDOM.render(
    <BrowserRouter>
      <App />
    </BrowserRouter>,
    div
      );
      ReactDOM.unmountComponentAtNode(div);
    })
    

    【讨论】:

    • Invariant Violation:元素类型无效:期望字符串(对于内置组件)或类/函数(对于复合组件),但得到:对象。
    • 不变违规:在“Connect(BaseLayout)”的上下文或道具中找不到“store”。要么将根组件包装在 中,要么将“store”作为道具显式传递给“Connect(BaseLayout)”。
    【解决方案4】:

    我遇到了同样的问题,第一条评论帮助了我,但是有很多代码 我有更好的方法来解决这个问题。 请参阅下面的解决方案:

        import React from 'react';
    import { shallow, mount } from 'enzyme';
    import SisuSignIn from '../../../components/Sisu/SisuSignIn.js';
    import { MemoryRouter } from 'react-router-dom';
    
    const Container = SisuSignIn;
    
    let wrapper;
    
    beforeEach(() => {
      wrapper = shallow(<Container />);
    });
    
    describe('SisuSignIn', () => {
      it('renders correctly', () => {
        expect(wrapper).toMatchSnapshot();
      });
      it('should render one <h1>', () => {
        const wrapper = mount(
          <MemoryRouter>
            <SisuSignIn auth={{ loading: false }} />
          </MemoryRouter>
        );
        expect(wrapper.find('div').length).toBe(12);
      });
    });
    

    【讨论】:

    • 这条评论应该更高,因为它有效。
    【解决方案5】:

    A 将您的“URL”历史记录在内存中(不会读取或写入地址栏)。在测试和 React Native 等非浏览器环境中很有用。

    我得到了类似的错误解决方案是在 Memory router 的帮助下包装组件

    import { MemoryRouter } from 'react-router'
    
    <MemoryRouter>
      <App/>
    </MemoryRouter>
    

    【讨论】:

    • 是的,为了测试 MemoryRouter 而不是 BrowserRouter。
    【解决方案6】:

    实际上,我曾经使用react-test-renderer 为我的组件创建快照测试,我遇到了这个问题,我所做的只是用BrowserRouter 包装我的组件,react-router-dom 附带,你可以这样做如下:

    import React from 'react'
    import { BrowserRouter } from 'react-router-dom'
    import renderer from "react-test-renderer";
    
    
    import UserListComponent from './whatever'
    
    
    
    test('some description', () => {
      
      const props = { ... }
    
      const jsx = (
        <BrowserRouter>
          <UserListComponent {...props} />
        </BrowserRouter>
      )
    
      const tree = renderer.create(jsx).toJSON()
      expect(tree).toMatchSnapshot()
    
    })
    
    

    如果你想多次测试这种情况,完全推荐使用测试工厂!

    【讨论】:

      【解决方案7】:

      添加Router标签来渲染它。

      import * as React from "react";
      import { render, mount } from 'enzyme';
      import { BrowserRouter as Router } from "react-router-dom"
      import CategoriesToBag from "../CategoriesToBag";
      
      
      
      describe('Testing CategoriesToBag Component', () => {
          test("check if heading is correct", () => {
              const defaultValue = "Amazing Categories To Bag";
              const wrapper = render(<Router><CategoriesToBag /></Router>);
              expect(wrapper.find('.textBannerTitle').text()).toMatch(/Categories To Bag/);
              expect(wrapper.find('.textBannerTitle').text()).not.toMatch(/Wrong Match/);
              expect(wrapper.find('.textBannerTitle').text()).toBe(defaultValue);
          });
      
      });
      

      你应该很高兴。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2021-01-16
        • 2018-11-08
        • 2021-08-09
        • 1970-01-01
        • 2019-05-28
        • 1970-01-01
        相关资源
        最近更新 更多