不变违规:无法在"Connect(SportsDatabase)"的上下文或道具中找到"存储"

114 mocha.js reactjs redux

完整代码: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;
    });

});
Run Code Online (Sandbox Code Playgroud)

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

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

错误

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)
Run Code Online (Sandbox Code Playgroud)

mar*_*son 154

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

你有几个选择:

  • 创建商店并<Provider>在连接的组件周围渲染
  • 创建商店并直接传递<MyConnectedComponent 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.

总的来说,我会注意到在这个组件中可能会尝试做太多.您可能需要考虑将其拆分为更小的部分,每个组件的逻辑更少.

  • 回答对“这很简单”这个短语感到困惑或困惑的人可能会显得轻蔑或苛刻。请谨慎使用。 (4认同)

小智 76

可能的解决方案对我来说很开心

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);
    });
});
Run Code Online (Sandbox Code Playgroud)

  • 谢谢,非常好的解决方案。我遇到了这个问题,因为我使用的是带有路由的顶级 App 组件,并且商店在每个路由中提供给子应用程序,因此我不必将道具传递到路由器中。我对它进行了一些更改以供我使用。const wrapper = Shallow(&lt;Provider store={store}&gt;&lt;App /&gt;&lt;/Provider&gt;); 期望(wrapper.contains(&lt;App /&gt;)).toBe(true); (2认同)

Vis*_*ati 62

正如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)
Run Code Online (Sandbox Code Playgroud)

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

// Note the curly braces: grab the named export instead of default export
import { App } from './App'
Run Code Online (Sandbox Code Playgroud)

如果你需要两个:

import ConnectedApp, { App } from './App'
Run Code Online (Sandbox Code Playgroud)

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

import App from './App'
Run Code Online (Sandbox Code Playgroud)

您只能使用命名导出进行测试.

  • 这是让我的测试再次通过的最快,最简单的方法。 (2认同)
  • “您将只使用命名的导出进行测试。” -为我工作。 (2认同)

Dan*_*iel 6

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

该Provider标记然后呈现您的父组件,让我们称之为组件,该App组件又呈现应用程序内的每个其他组件.

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

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

多数民众赞成你想要发生的事情,但在你的测试环境中,流动正在崩溃.

为什么?

为什么?

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

所以你在测试文件中所做的基本上就是把它放在那个组件上,然后把它扔掉,并且与它Provider上面的任何或存储都没有联系,这就是你看到这个消息的原因.

在Provider该组件的上下文或prop中没有存储或标记,因此组件会抛出错误,因为它希望Provider在其父层次结构中看到标记或存储.

这就是错误的含义.


小智 6

我升级的时候也出现过这种情况。我不得不降级回来。

\n\n

反应 Redux ^5.0.6 \xe2\x86\x92 ^7.1.3

\n


jos*_*405 5

就我而言

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

const store: any = createStore(
  myReducers,
  applyMiddleware(thunk)
);
Run Code Online (Sandbox Code Playgroud)

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


ATQ*_*SHL 5

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

代替

import connect from "react-redux/lib/connect/connect";
Run Code Online (Sandbox Code Playgroud)

和

import {connect} from "react-redux";
Run Code Online (Sandbox Code Playgroud)