不变违规: 无法在“ Connect (SportsDatabase)”的上下文或道具中找到“ store”

完整代码: 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)
172849 次浏览

很简单。您试图测试通过调用 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

总的来说,我要指出的是,您可能试图在这个组件中做太多的工作,并且可能需要考虑将其分成更小的部分,每个组件的逻辑更少。

正如官方 医生的还原建议,更好地输出未连接的组件以及。

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

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

可能的解决办法,我的工作与玩笑

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

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

这个 Provider标签然后呈现您的父组件,让我们称之为 App组件,它反过来呈现应用程序内的每个其他组件。

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

所以当你把 connect()函数放进去的时候它会查找层次结构并尝试找到 Provider

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

为什么?

为什么?

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

所以实际上你在测试文件中所做的就是获取那个组件然后把它放到野外它与任何 Provider都没有联系或者存储在它上面这就是为什么你看到这条消息。

在该组件的上下文或支持中没有 store 或 Provider标记,因此该组件抛出一个错误,因为它希望在其父层次结构中看到一个 Provider标记或 store。

这就是那个错误的意思。

对我来说

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


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

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

只要从“酵素”导入{浅,装入} ;

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


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


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

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

更换

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

import {connect} from "react-redux";

我升级的时候就这样了,我不得不降级回去。

反应-还原 ^ 5.0.6→ ^ 7.1.3

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

添加导致此错误的库方法模拟。在我的案例中,这发生在 react-native-offlineuseIsConnected上。所以我嘲笑它如下:

jest.mock("react-native-offline", () => ({ useIsConnected: jest.fn() }));