永久违反:在“ Connect(SportsDatabase)”的上下文或道具中找不到“存储”


142

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

你好

  • 我有一个应用程序,其中根据构建环境显示了针对台式机和移动设备的不同模板。
  • 我可以在需要隐藏移动模板的导航菜单的地方成功开发它。
  • 现在,我能够编写一个测试用例,其中它通过原型获取所有值并正确呈现
  • 但不确定如何在移动设备上编写单元测试用例时,不应呈现导航组件。
  • 我尝试过,但是遇到错误...您能告诉我如何解决它。
  • 提供下面的代码。

测试用例

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;
    });

});

需要编写测试用例的代码片段

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)

Answers:


182

很简单 您正在尝试测试调用生成的包装器组件connect()(MyPlainComponent)。该包装器组件希望可以访问Redux存储。通常情况下,该存储库可以用作context.store,因为在组件层次结构的顶部,您会有一个<Provider store={myStore} />。但是,您将自己渲染连接的组件,没有存储,因此会引发错误。

您有几种选择:

  • 创建一个商店并<Provider>在连接的组件周围渲染一个
  • 创建一个商店并将其直接传递为<MyConnectedComponent store={store} />,因为连接的组件也将接受“ store”作为道具
  • 不要费心测试连接的组件。导出“普通”未连接版本,然后进行测试。如果您测试您的普通组件和mapStateToProps功能,则可以放心地假设所连接的版本将正常工作。

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

编辑

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

此时,您有两个新选项:

  • 仅对SportsTopPane进行“浅”渲染,以免强迫其完全渲染其子级
  • 如果您确实想对SportsTopPane进行“深度”渲染,则需要在上下文中提供Redux存储。我强烈建议您看一下酶测试库,它可以让您做到这一点。有关示例,请参见http://airbnb.io/enzyme/docs/api/ReactWrapper/setContext.html

总体而言,我会指出您可能在此组件中尝试做太多事情,并且可能希望考虑将其分解为较小的组件,而每个组件的逻辑较少。


我试过但不确定如何做...您可以在我的测试用例中进行更新吗

1
我假设在SportsTopPortion.js中,您拥有let SportsTopPortion = connect(mapStateToProps)(SomeOtherComponent)。最简单的答案是测试其他组件,而不是测试的组件connect
markerikson

1
啊哈 现在我看到发生了什么事。问题不在于SportsTopPane本身。问题在于您正在执行SportsTopPane的“完整”渲染,而不是“浅”渲染,因此React试图完全渲染所有子项。错误消息指向该行searchComponent = <SportsDatabase sportsWholeFramework="desktop" />;是期望存储和中断的连接的组件。因此,有两个新建议:要么仅对SportsTopPane进行浅显示,要么使用像酶这样的库进行测试。请参阅airbnb.io/enzyme/docs/api/ReactWrapper/setContext.html
markerikson16年

您能告诉我如何为这种情况编写测试用例吗?”,但不确定在移动时不应呈现导航组件时如何编写单元测试用例。```

3
用“很简单”这个短语来回答那些被卡住或困惑的人可能会贬低或苛刻。请谨慎使用。
jayqui

97

可能对我有用的解决方案

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);
    });
});

1
效果很好,而不必一一传递道具。
ghostkraviz

2
谢谢,一个非常好的解决方案。我遇到了这个问题,因为我在路由中使用了顶层App组件,并且存储在每个路由中都提供给子应用,因此我不必将prop传递到路由器中。我作了些改动以供使用。const包装=浅(<Provider store = {store}> <App /> </ Provider>); Expect(wrapper.contains(<App />)).toBe(true);
Little Brain

69

正如redux 的官方文档所建议的,最好也导出未连接的组件。

为了能够测试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)

由于默认的导出仍然是经过修饰的组件,因此上图所示的import语句将像以前一样工作,因此您不必更改应用程序代码。但是,您现在可以像这样将未修饰的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'

您将只使用命名的导出进行测试。


1
这个答案也是合法的。编辑了您的链接以匹配锚点。
Erowlin '18 -4-9

这个答案很合理!并非在所有情况下都是正确的选择,但绝对比没有必要与提供程序一起玩要好。
lokori

谢谢@lokori快乐,喜欢它!
维沙尔·古拉提

2
这是使我的测试再次通过的最快,最简单的方法。
迈克·里昂斯

2
“您将只使用命名的导出进行测试。” -为我工作。
technazi

7

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

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

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

因此,您将connect()函数放在其中的实例将查找层次结构,并尝试找到Provider

那就是您想要发生的事情,但是在您的测试环境中,流程正在崩溃。

为什么?

为什么?

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

因此,实质上,您在该测试文件中所做的只是获取该组件并将其扔掉而已,它与任何文件都没有关系 Provider存储或存储,这就是为什么您看到此消息。

Provider该组件的上下文或属性中没有存储或标记,因此该组件引发错误,因为它希望看到一个Provider在其父层次结构中标记或存储。

这就是该错误的含义。


6

就我而言

const myReducers = combineReducers({
  user: UserReducer
});

const store: any = createStore(
  myReducers,
  applyMiddleware(thunk)
);

shallow(<Login />, { context: { store } });


2

这样做是从“酶”导入{浅,安装};

const store = mockStore({
  startup: { complete: false }
});

describe("==== Testing App ======", () => {
  const setUpFn = props => {
    return mount(
      <Provider store={store}>
        <App />
      </Provider>
    );
  };

  let wrapper;
  beforeEach(() => {
    wrapper = setUpFn();
  });

2

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

更换

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

import {connect} from "react-redux";


0

在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'));

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.