【问题标题】:Invariant Violation: Could not find "store" in either the context or props of "Connect(SportsDatabase)"不变违规:在“Connect(SportsDatabase)”的上下文或道具中找不到“商店”
【发布时间】:2016-07-12 17:24:37
【问题描述】:

完整代码在这里:https://gist.github.com/js08/0ec3d70dfda76d7e9fb4

嗨,

  • 我有一个应用程序,它根据构建环境显示桌面和移动设备的不同模板。
  • 我成功地开发了它,我需要隐藏我的移动模板的导航菜单。
  • 现在我可以编写一个测试用例,它通过 proptypes 获取所有值并正确呈现
  • 但不确定如何编写单元测试用例,因为它的移动设备不应该呈现导航组件。
  • 我试过了,但我遇到了一个错误...你能告诉我如何解决它吗?
  • 下面提供代码。

测试用例

import {expect} from 'chai';
import React from 'react';
import TestUtils from 'react-addons-test-utils';
import {SportsTopPortion} from '../../../src/components/sports-top-portion/sports-top-portion.jsx';
require('../../test-utils/dom');


describe('"sports-top-portion" Unit Tests', function() {
    let shallowRenderer = TestUtils.createRenderer();

    let sportsContentContainerLayout ='mobile';
    let sportsContentContainerProfile = {'exists': 'hasSidebar'};
    let sportsContentContainerAuthExchange = {hasValidAccessToken: true};
    let sportsContentContainerHasValidAccessToken ='test'; 

    it('should render correctly', () => {
        shallowRenderer.render(<SportsTopPortion sportsWholeFramework={sportsContentContainerLayout} sportsPlayers={sportsContentContainerProfile} sportsAuthentication={sportsContentContainerAuthExchange} sportsUpperBar={{activeSportsLink:'test'}} />);
        //shallowRenderer.render(<SportsTopPortion sportsWholeFramework={sportsContentContainerLayout} sportsPlayers={sportsContentContainerProfile} hasValidAccessToken={sportsContentContainerHasValidAccessToken}  />);

        let renderedElement = shallowRenderer.getRenderOutput();
        console.log("renderedElement------->" + JSON.stringify(renderedElement));

        expect(renderedElement).to.exist;
    });

    it('should not render sportsNavigationComponent when sports.build is mobile', () => {
        let sportsNavigationComponent = TestUtils.renderIntoDocument(<SportsTopPortion sportsWholeFramework={sportsContentContainerLayout} sportsPlayers={sportsContentContainerProfile} sportsAuthentication={sportsContentContainerAuthExchange} sportsUpperBar={{activeSportsLink:'test'}} />);
        console.log("sportsNavigationComponent------->" + JSON.stringify(sportsNavigationComponent));

        //let footnoteContainer = TestUtils.findRenderedDOMComponentWithClass(sportsNavigationComponent, 'linkPack--standard');

        //expect(footnoteContainer).to.exist;
    });

});

需要编写测试用例的代码sn-p

if (sports.build === 'mobile') {
    sportsNavigationComponent = <div />;
    sportsSideMEnu = <div />;
    searchComponent = <div />;
    sportsPlayersWidget = <div />;
}

错误

1) "sports-top-portion" Unit Tests should not render sportsNavigationComponent when sports.build is mobile:
     Invariant Violation: Could not find "store" in either the context or props of "Connect(SportsDatabase)". Either wrap the root component in a <Provider>, or explicitly pass "store" as a prop to "Connect(SportsDatabase)".
      at Object.invariant [as default] (C:\sports-whole-page\node_modules\invariant\invariant.js:42:15)
      at new Connect (C:\sports-whole-page\node_modules\react-redux\lib\components\createConnect.js:135:33)
      at [object Object].ReactCompositeComponentMixin.mountComponent (C:\sports-whole-page\node_modules\react\lib\ReactCompositeComponent.js:148:18)
      at [object Object].wrapper [as mountComponent] (C:\sports-whole-page\node_modules\react\lib\ReactPerf.js:66:21)
      at Object.ReactReconciler.mountComponent (C:\sports-whole-page\node_modules\react\lib\ReactReconciler.js:37:35)
      at ReactDOMComponent.ReactMultiChild.Mixin.mountChildren (C:\sports-whole-page\node_modules\react\lib\ReactMultiChild.js:241:44)
      at ReactDOMComponent.Mixin._createContentMarkup (C:\sports-whole-page\node_modules\react\lib\ReactDOMComponent.js:591:32)
      at ReactDOMComponent.Mixin.mountComponent (C:\sports-whole-page\node_modules\react\lib\ReactDOMComponent.js:479:29)
      at Object.ReactReconciler.mountComponent (C:\sports-whole-page\node_modules\react\lib\ReactReconciler.js:37:35)
      at ReactDOMComponent.ReactMultiChild.Mixin.mountChildren (C:\sports-whole-page\node_modules\react\lib\ReactMultiChild.js:241:44)
      at ReactDOMComponent.Mixin._createContentMarkup (C:\sports-whole-page\node_modules\react\lib\ReactDOMComponent.js:591:32)
      at ReactDOMComponent.Mixin.mountComponent (C:\sports-whole-page\node_modules\react\lib\ReactDOMComponent.js:479:29)
      at Object.ReactReconciler.mountComponent (C:\sports-whole-page\node_modules\react\lib\ReactReconciler.js:37:35)
      at [object Object].ReactCompositeComponentMixin.mountComponent (C:\sports-whole-page\node_modules\react\lib\ReactCompositeComponent.js:225:34)
      at [object Object].wrapper [as mountComponent] (C:\sports-whole-page\node_modules\react\lib\ReactPerf.js:66:21)
      at Object.ReactReconciler.mountComponent (C:\sports-whole-page\node_modules\react\lib\ReactReconciler.js:37:35)
      at ReactDOMComponent.ReactMultiChild.Mixin.mountChildren (C:\sports-whole-page\node_modules\react\lib\ReactMultiChild.js:241:44)
      at ReactDOMComponent.Mixin._createContentMarkup (C:\sports-whole-page\node_modules\react\lib\ReactDOMComponent.js:591:32)
      at ReactDOMComponent.Mixin.mountComponent (C:\sports-whole-page\node_modules\react\lib\ReactDOMComponent.js:479:29)
      at Object.ReactReconciler.mountComponent (C:\sports-whole-page\node_modules\react\lib\ReactReconciler.js:37:35)
      at [object Object].ReactCompositeComponentMixin.mountComponent (C:\sports-whole-page\node_modules\react\lib\ReactCompositeComponent.js:225:34)
      at [object Object].wrapper [as mountComponent] (C:\sports-whole-page\node_modules\react\lib\ReactPerf.js:66:21)
      at Object.ReactReconciler.mountComponent (C:\sports-whole-page\node_modules\react\lib\ReactReconciler.js:37:35)
      at ReactDOMComponent.ReactMultiChild.Mixin.mountChildren (C:\sports-whole-page\node_modules\react\lib\ReactMultiChild.js:241:44)
      at ReactDOMComponent.Mixin._createContentMarkup (C:\sports-whole-page\node_modules\react\lib\ReactDOMComponent.js:591:32)
      at ReactDOMComponent.Mixin.mountComponent (C:\sports-whole-page\node_modules\react\lib\ReactDOMComponent.js:479:29)
      at Object.ReactReconciler.mountComponent (C:\sports-whole-page\node_modules\react\lib\ReactReconciler.js:37:35)
      at [object Object].ReactCompositeComponentMixin.mountComponent (C:\sports-whole-page\node_modules\react\lib\ReactCompositeComponent.js:225:34)
      at [object Object].wrapper [as mountComponent] (C:\sports-whole-page\node_modules\react\lib\ReactPerf.js:66:21)
      at Object.ReactReconciler.mountComponent (C:\sports-whole-page\node_modules\react\lib\ReactReconciler.js:37:35)
      at [object Object].ReactCompositeComponentMixin.mountComponent (C:\sports-whole-page\node_modules\react\lib\ReactCompositeComponent.js:225:34)
      at [object Object].wrapper [as mountComponent] (C:\sports-whole-page\node_modules\react\lib\ReactPerf.js:66:21)
      at Object.ReactReconciler.mountComponent (C:\sports-whole-page\node_modules\react\lib\ReactReconciler.js:37:35)
      at mountComponentIntoNode (C:\sports-whole-page\node_modules\react\lib\ReactMount.js:266:32)
      at ReactReconcileTransaction.Mixin.perform (C:\sports-whole-page\node_modules\react\lib\Transaction.js:136:20)
      at batchedMountComponentIntoNode (C:\sports-whole-page\node_modules\react\lib\ReactMount.js:282:15)
      at ReactDefaultBatchingStrategyTransaction.Mixin.perform (C:\sports-whole-page\node_modules\react\lib\Transaction.js:136:20)
      at Object.ReactDefaultBatchingStrategy.batchedUpdates (C:\sports-whole-page\node_modules\react\lib\ReactDefaultBatchingStrategy.js:62:19)
      at Object.batchedUpdates (C:\sports-whole-page\node_modules\react\lib\ReactUpdates.js:94:20)
      at Object.ReactMount._renderNewRootComponent (C:\sports-whole-page\node_modules\react\lib\ReactMount.js:476:18)
      at Object.wrapper [as _renderNewRootComponent] (C:\sports-whole-page\node_modules\react\lib\ReactPerf.js:66:21)
      at Object.ReactMount._renderSubtreeIntoContainer (C:\sports-whole-page\node_modules\react\lib\ReactMount.js:550:32)
      at Object.ReactMount.render (C:\sports-whole-page\node_modules\react\lib\ReactMount.js:570:23)
      at Object.wrapper [as render] (C:\sports-whole-page\node_modules\react\lib\ReactPerf.js:66:21)
      at Object.ReactTestUtils.renderIntoDocument (C:\sports-whole-page\node_modules\react\lib\ReactTestUtils.js:76:21)
      at Context.<anonymous> (C:/codebase/sports-whole-page/test/components/sports-top-portion/sports-top-portion-unit-tests.js:28:41)
      at callFn (C:\sports-whole-page\node_modules\mocha\lib\runnable.js:286:21)
      at Test.Runnable.run (C:\sports-whole-page\node_modules\mocha\lib\runnable.js:279:7)
      at Runner.runTest (C:\sports-whole-page\node_modules\mocha\lib\runner.js:421:10)
      at C:\sports-whole-page\node_modules\mocha\lib\runner.js:528:12
      at next (C:\sports-whole-page\node_modules\mocha\lib\runner.js:341:14)
      at C:\sports-whole-page\node_modules\mocha\lib\runner.js:351:7
      at next (C:\sports-whole-page\node_modules\mocha\lib\runner.js:283:14)
      at Immediate._onImmediate (C:\sports-whole-page\node_modules\mocha\lib\runner.js:319:5)

【问题讨论】:

    标签: reactjs mocha.js redux


    【解决方案1】:

    这很简单。您正在尝试测试通过调用connect()(MyPlainComponent) 生成的包装器组件。该包装器组件期望能够访问 Redux 存储。通常,该商店以context.store 的形式提供,因为在组件层次结构的顶部,您将拥有&lt;Provider store={myStore} /&gt;。但是,您自己渲染连接的组件,没有存储,所以它会引发错误。

    你有几个选择:

    • 创建一个商店并在您连接的组件周围呈现一个&lt;Provider&gt;
    • 创建一个 store 并直接将其作为 &lt;MyConnectedComponent store={store} /&gt; 传入,因为连接的组件也将接受“store”作为道具
    • 不要费心测试连接的组件。导出“普通”、未连接的版本,然后对其进行测试。如果您测试您的普通组件和您的 mapStateToProps 函数,您可以放心地假设连接的版本将正常工作。

    您可能想通读 Redux 文档中的“测试”页面:https://redux.js.org/recipes/writing-tests

    编辑

    在实际看到您发布的源代码并重新阅读错误消息后,真正的问题不在于 SportsTopPane 组件。问题是您正在尝试“完全”渲染 SportsTopPane,它也渲染其所有子项,而不是像在第一种情况下那样进行“浅”渲染。 searchComponent = &lt;SportsDatabase sportsWholeFramework="desktop" /&gt;; 行正在渲染一个我假设也是连接的组件,因此期望在 React 的“上下文”功能中可以使用存储。

    此时,您有两个新选择:

    • 只对 SportsTopPane 进行“浅层”渲染,这样您就不会强制它完全渲染其子项
    • 如果您确实想要对 SportsTopPane 进行“深度”渲染,则需要在上下文中提供 Redux 存储。我强烈建议你看看 Enzyme testing library,它可以让你做到这一点。有关示例,请参阅 http://airbnb.io/enzyme/docs/api/ReactWrapper/setContext.html

    总的来说,我会注意到您可能试图在这个组件中做太多事情,并且可能想要考虑将其分解成更小的部分,每个组件的逻辑更少。

    【讨论】:

    • 我试过但不知道怎么做...你能在我的测试用例中更新
    • 我假设在 SportsTopPortion.js 中,您有 let SportsTopPortion = connect(mapStateToProps)(SomeOtherComponent)。最简单的答案是测试 other 组件,而不是 connect 返回的组件。
    • 啊哈。现在我明白发生了什么。问题不在于 SportsTopPane 本身。问题是您正在对 SportsTopPane 进行“完整”渲染,而不是“浅”渲染,因此 React 正在尝试完全渲染所有子项。错误消息指的是searchComponent = &lt;SportsDatabase sportsWholeFramework="desktop" /&gt;; 行。 That 是连接组件,它期待存储和中断。所以,两个新的建议:要么只做 SportsTopPane 的浅渲染,要么使用像 Enzyme 这样的库来测试。见airbnb.io/enzyme/docs/api/ReactWrapper/setContext.html
    • 你能告诉我如何编写这个场景的测试用例``` 但不知道如何编写单元测试用例,当它的移动它不应该呈现导航组件时。 ```
    • 回答那些对“这很简单”这个短语感到困惑或困惑的人可能会被认为是贬低或苛刻。请谨慎使用。
    【解决方案2】:

    玩笑对我有用的可能解决方案

    import React from "react";
    import { shallow } from "enzyme";
    import { Provider } from "react-redux";
    import configureMockStore from "redux-mock-store";
    import TestPage from "../TestPage";
    
    const mockStore = configureMockStore();
    const store = mockStore({});
    
    describe("Testpage Component", () => {
        it("should render without throwing an error", () => {
            expect(
                shallow(
                    <Provider store={store}>
                        <TestPage />
                    </Provider>
                ).exists(<h1>Test page</h1>)
            ).toBe(true);
        });
    });
    

    【讨论】:

    • 效果很好,不用一个个传递props。
    • 谢谢,一个非常好的解决方案。我遇到了这个问题,因为我正在使用带有路由的顶级 App 组件,并且商店在每个路由中都提供给子应用程序,因此我不必将道具传递给路由器。为了我的使用,我稍微改变了它。 const wrapper = shallow();期望(wrapper.contains()).toBe(true);
    【解决方案3】:

    正如redux官方docs建议的那样,最好将未连接的组件也导出。

    为了能够在不处理装饰器的情况下测试 App 组件本身,我们建议您也导出未装饰的组件:

    import { connect } from 'react-redux'
    
    // Use named export for unconnected component (for tests)
    export class App extends Component { /* ... */ }
     
    // Use default export for the connected component (for app)
    export default connect(mapStateToProps)(App)
    

    由于默认导出仍然是装饰组件,因此上图中的导入语句将像以前一样工作,因此您不必更改应用程序代码。但是,您现在可以像这样在测试文件中导入未修饰的 App 组件:

    // Note the curly braces: grab the named export instead of default export
    import { App } from './App'
    

    如果你同时需要:

    import ConnectedApp, { App } from './App'
    

    在应用程序本身中,您仍然可以正常导入:

    import App from './App'
    

    您只能将命名导出用于测试。

    【讨论】:

    • 这个答案也是合法的。编辑您的链接以匹配锚点。
    • 这个答案很有意义!这可能不是在所有情况下都是正确的,但绝对比在没有必要时使用 Provider 和所有这些要好。
    • 谢谢@lokori 很高兴你喜欢它!
    • 这是让我的测试再次通过的最快、最简单的方法。
    • "您只能将命名导出用于测试。" -- 适合我。
    【解决方案4】:

    当我们组装一个 react-redux 应用程序时,我们应该期望看到一个结构,在该结构的顶部我们有一个 Provider 标签,它有一个 redux 存储的实例。

    Provider 标记然后呈现您的父组件,我们将其称为 App 组件,该组件依次呈现应用程序内的所有其他组件。

    这是关键部分,当我们使用connect() 函数包装一个组件时,connect() 函数期望在具有Provider 标签的层次结构中看到某个父组件。

    所以你把connect()函数放在那里的实例,它会查找层次结构并尝试找到Provider

    这就是您希望发生的事情,但是在您的测试环境中,流程正在崩溃。

    为什么?

    为什么?

    当我们回到假设的 sportsDatabase 测试文件时,您必须自己是 sportsDatabase 组件,然后尝试单独呈现该组件。

    所以本质上,您在该测试文件中所做的只是获取该组件并将其扔到野外,它与任何Provider 或存储在其上方没有任何关系,这就是您看到此消息的原因。

    该组件的 context 或 prop 中没有 store 或 Provider 标签,因此该组件会抛出错误,因为它希望在其父层次结构中查看 Provider 标签或 store。

    这就是那个错误的意思。

    【讨论】:

      【解决方案5】:

      就我而言

      const myReducers = combineReducers({
        user: UserReducer
      });
      
      const store: any = createStore(
        myReducers,
        applyMiddleware(thunk)
      );
      

      shallow(&lt;Login /&gt;, { context: { store } });

      【讨论】:

        【解决方案6】:

        对我来说这是导入问题,希望对您有所帮助。 WebStorm 的默认导入错误。

        替换

        import connect from "react-redux/lib/connect/connect";
        

        import {connect} from "react-redux";
        

        【讨论】:

          【解决方案7】:

          这发生在我升级时。我不得不降级回来。

          react-redux ^5.0.6 → ^7.1.3

          【讨论】:

          【解决方案8】:

          只需从“酶”导入 { shallow, mount };

          const store = mockStore({
            startup: { complete: false }
          });
          
          describe("==== Testing App ======", () => {
            const setUpFn = props => {
              return mount(
                <Provider store={store}>
                  <App />
                </Provider>
              );
            };
          
            let wrapper;
            beforeEach(() => {
              wrapper = setUpFn();
            });
          

          【讨论】:

            【解决方案9】:

            在你的 Index.js 末尾需要添加这段代码:

            import React from 'react';
            import ReactDOM from 'react-dom';
            import { BrowserRouter  } from 'react-router-dom';
            
            import './index.css';
            import App from './App';
            
            import { Provider } from 'react-redux';
            import { createStore, applyMiddleware, compose, combineReducers } from 'redux';
            import thunk from 'redux-thunk';
            
            ///its your redux ex
            import productReducer from './redux/reducer/admin/product/produt.reducer.js'
            
            const rootReducer = combineReducers({
                adminProduct: productReducer
               
            })
            const composeEnhancers = window._REDUX_DEVTOOLS_EXTENSION_COMPOSE_ || compose;
            const store = createStore(rootReducer, composeEnhancers(applyMiddleware(thunk)));
            
            
            const app = (
                <Provider store={store}>
                    <BrowserRouter   basename='/'>
                        <App />
                    </BrowserRouter >
                </Provider>
            );
            ReactDOM.render(app, document.getElementById('root'));

            【讨论】:

              猜你喜欢
              • 2023-03-24
              • 2018-10-08
              • 2019-09-28
              • 2016-04-30
              • 2017-12-02
              • 1970-01-01
              • 1970-01-01
              • 2020-04-15
              相关资源
              最近更新 更多