web-dev-qa-db-ja.com

不変違反: "Connect(SportsDatabase)"の文脈や小道具のどちらにも "store"が見つかりませんでした

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

こんにちは、

  • 私はそれがビルド環境に基づいてデスクトップ用とモバイル用に異なるテンプレートを表示するアプリケーションを持っています。
  • モバイルテンプレートのナビゲーションメニューを非表示にする必要がある場合は、正常に開発できました。
  • 今、私はそれがproptypeを通してすべての値を取ってきて正しくレンダリングするという1つのテストケースを書くことができます
  • しかし、そのモバイルそれはNAVコンポーネントをレンダリングするべきではないときにどのようにユニットテストケースを書くのかわからない。
  • 私は試したが、私はエラーに直面しています...あなたはそれを修正する方法を教えてもらえますか。
  • 以下のコードを提供します。

テストケース

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)
84
user6015171

とても簡単です。 connect()(MyPlainComponent)を呼び出して生成されたラッパーコンポーネントをテストしようとしています。そのラッパーコンポーネントは、Reduxストアへのアクセス権を持つことを期待しています。通常、そのストアはcontext.storeとして使用できます。コンポーネント階層の最上位には<Provider store={myStore} />があるからです。しかし、あなたはあなたの接続されたコンポーネントを単独で、ストアなしでレンダリングしているので、それはエラーを投げます。

いくつかの選択肢があります。

  • ストアを作成し、接続コンポーネントの周囲に<Provider>をレンダリングします。
  • 接続コンポーネントも支柱として "store"を受け入れるため、ストアを作成して<MyConnectedComponent store={store} />として直接渡します。
  • 接続コンポーネントのテストを気にしないでください。 「普通の」未接続のバージョンをエクスポートして、代わりにそれをテストしてください。あなたのプレーンなコンポーネントとあなたのmapStateToProps関数をテストするならば、あなたは安全に接続されたバージョンが正しく働くであろうと仮定することができます。

あなたはおそらくReduxのドキュメントの "Testing"ページを読みたいでしょう: https://redux.js.org/recipes/writing-tests

編集

実際にあなたがソースを投稿したことを確認し、エラーメッセージを読み直すと、本当の問題はSportsTopPaneコンポーネントにはありません。問題は、SportsTopPaneを「完全に」レンダリングしようとしていることです。これは、最初のケースのように「浅い」レンダリングを行うのではなく、すべての子をレンダリングすることです。 searchComponent = <SportsDatabase sportsWholeFramework="desktop" />;という行は、私もまた接続されていると思うコンポーネントをレンダリングしているので、Reactの "コンテキスト"機能でストアが利用可能になることを期待しています。

この時点で、2つの新しい選択肢があります。

  • SportsTopPaneの「浅い」レンダリングのみを行います。そのため、子を完全にレンダリングするように強制されているわけではありません。
  • SportsTopPaneを「深く」レンダリングしたい場合は、コンテキストに応じてReduxストアを提供する必要があります。 Enzymeテストライブラリを見てみることを強くお勧めします。例については、 http://airbnb.io/enzyme/docs/api/ReactWrapper/setContext.html を参照してください。

全体的に見て、私はこの1つのコンポーネントでやりすぎている可能性があることに気付くでしょう。コンポーネントあたりのロジックを少なくして、これを小さな部分に分割することを検討してください。

132
markerikson

Jestで私のために働いた可能な解決策

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

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)

デフォルトのエクスポートはまだ装飾されたコンポーネントなので、上の図の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'

テストには名前付きエクスポートのみを使用します。

48
Vishal Gulati

React-reduxアプリケーションをまとめると、トップにreduxストアのインスタンスを持つProviderタグがある構造が表示されるはずです。

そのProviderタグは、それからあなたの親コンポーネントをレンダリングし、それをAppコンポーネントと呼びましょう。それは次に、アプリケーション内の他のすべてのコンポーネントをレンダリングします。

connect()関数でコンポーネントをラップするとき、ここで重要なのはconnect()関数がProviderタグを持つ階層内の親コンポーネントを見ることを期待していることです。

そこで、connect()関数をそこに入れたインスタンスは、階層を調べてProviderを見つけようとします。

あなたが何をしたいのかというと、テスト環境ではその流れは崩れています。

どうして?

どうして?

想定されているsportsDatabaseテストファイルに戻ると、あなたはそれ自身でsportsDatabaseコンポーネントでなければならず、それからそのコンポーネントを単独で単独でレンダリングしようとします。

そのため、基本的にそのテストファイルの内部で行っていることは、そのコンポーネントを取り出して実際に使用しないことであり、Providerやその上のストアとは関係がないため、このメッセージが表示されます。

そのコンポーネントのコンテキストまたはプロップにstoreまたはProviderタグがないため、Providerタグまたはその親階層内のストアを表示したいため、コンポーネントはエラーをスローします。

つまり、それがそのエラーの意味です。

6
Daniel

私の場合は

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

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

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

5
jose920405