不変の違反:「Connect(SportsDatabase)」のコンテキストまたは小道具のいずれにも「ストア」が見つかりませんでした


142

ここに完全なコード:https : //gist.github.com/js08/0ec3d70dfda76d7e9fb4

こんにちは、

  • ビルド環境に基づいてデスクトップ用とモバイル用の異なるテンプレートを表示するアプリケーションがあります。
  • モバイルテンプレートのナビゲーションメニューを非表示にする必要がある場所で、開発に成功しました。
  • 現在、proptypeを介してすべての値をフェッチし、正しくレンダリングする1つのテストケースを作成できます。
  • しかし、モバイルがナビゲーションコンポーネントをレンダリングしてはならない場合の単体テストケースの記述方法はわかりません。
  • 試しましたが、エラーが発生しています...修正方法を教えてください。
  • 以下のコードを提供します。

テストケース

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)

回答:


182

とても簡単です。を呼び出して生成されたラッパーコンポーネントをテストしようとしています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の「コンテキスト」機能でストアが使用可能になることを期待しています。

この時点で、2つの新しいオプションがあります。

  • SportsTopPaneの「浅い」レンダリングのみを行うため、子を完全にレンダリングする必要はありません。
  • SportsTopPaneの「ディープ」レンダリングを実行する場合は、コンテキストでReduxストアを提供する必要があります。まさにそれを可能にする酵素テストライブラリをご覧になることを強くお勧めします。例については、http://airbnb.io/enzyme/docs/api/ReactWrapper/setContext.htmlを参照してください

全体として、この1つのコンポーネントでやりすぎている可能性があり、コンポーネントごとのロジックが少ない小さな部分に分割することを検討する必要があるかもしれません。


試しましたが、どうすればいいのかわかりません...テストケースで更新できますか

1
私はSportsTopPortion.jsに、あなたが持っていると仮定していますlet SportsTopPortion = connect(mapStateToProps)(SomeOtherComponent)。最も簡単な答えは、によって返されるコンポーネントではなく、他のコンポーネントをテストすることconnectです。
markerikson 2016年

1
あは。今、私は何が起こっているのかわかります。問題はSportsTopPane自体にはありません。問題は、「浅い」レンダリングではなく、SportsTopPaneの「完全な」レンダリングを実行しているため、Reactがすべての子を完全にレンダリングしようとしていることです。エラーメッセージは行を参照していますsearchComponent = <SportsDatabase sportsWholeFramework="desktop" />;それは、ストアを期待して壊れている接続コンポーネントです。したがって、2つの新しい提案があります。SportsTopPaneの浅いレンダリングのみを行うか、Enzymeなどのライブラリを使用してテストします。airbnb.io/enzyme/docs/api/ReactWrapper/setContext.htmlを参照してください。
markerikson 2016年

このシナリオ `` `のテストケースの書き方を教えてもらえますが、モバイルがナビゲーションコンポーネントをレンダリングしてはならない場合の単体テストケースの書き方がわかりません。`` `

3
「それはかなり簡単だ」というフレーズで行き詰まったり困惑したりする人に答えることは、中傷的または過酷なものになる可能性があります。控えめにご利用ください。
ジェイキ

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
小道具を1つずつ渡す代わりに、うまく機能します。
ghostkraviz

2
とても良い解決策をありがとうございます。ルーティングで最上位のAppコンポーネントを使用していて、ストアが各ルートで子アプリに提供されているため、小道具をルーターに渡す必要がないため、この問題が発生しました。私は使用するために少し変更しました。const wrapper = shallow(<Provider store = {store}> <App /> </ Provider>); expect(wrapper.contains(<App />)).toBe(true);
リトルブレイン

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)

デフォルトのエクスポートは装飾されたコンポーネントであるため、上記のインポート文は以前と同じように機能するため、アプリケーションコードを変更する必要はありません。ただし、次のようにして、装飾されていないアプリコンポーネントをテストファイルにインポートできます。

// 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

この答えは完全に理にかなっています!それはすべてのケースで正しいことではないかもしれませんが、プロバイダーと一緒に遊んだり、それが必要ないときはそれよりも間違いなく優れています。
lokori

ありがとう@lokoriハッピーあなたはそれが好きです!
Vishal Gulati 2018

2
これは、テストに再度合格するための最も迅速で簡単な方法でした。
Mike Lyons

2
「名前付きエクスポートはテストにのみ使用します。」-私のために働く。
テクナジ

7

react-reduxアプリケーションを組み立てると、上部にProviderreduxストアのインスタンスを持つタグがある構造が表示されます。

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

jusは「酵素」からこのインポート{浅い、マウント}を行います。

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";

1

これは私がアップグレードしたときに起こりました。ダウングレードする必要がありました。

react-redux ^ 5.0.6→^ 7.1.3


これは、コメントよりもコメントです
sudo97

多くの重大な変更がありました。変更をよりよく理解するには、このビデオを視聴することをお勧めしますyoutube.com/watch?v=yOZ4Ml9LlWE
Kamil Dzieniszewski

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

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.