Pelanggaran Invarian: Tidak dapat menemukan "toko" baik dalam konteks atau alat peraga dari "Connect (SportsDatabase)"


142

Kode lengkap di sini: https://gist.github.com/js08/0ec3d70dfda76d7e9fb4

Hai,

  • Saya memiliki aplikasi di mana ia menunjukkan template yang berbeda untuk desktop dan seluler berdasarkan lingkungan build.
  • Saya berhasil mengembangkannya di mana saya perlu menyembunyikan menu navigasi untuk templat seluler saya.
  • saat ini saya dapat menulis satu test case di mana ia mengambil semua nilai melalui proptypes dan merender dengan benar
  • tetapi tidak yakin bagaimana menulis unit test case ketika mobile-nya seharusnya tidak membuat komponen nav.
  • Saya mencoba tetapi saya menghadapi kesalahan ... dapatkah Anda memberi tahu saya cara memperbaikinya?
  • kode provding di bawah ini.

Kasus cobaan

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

});

Cuplikan kode tempat test case perlu ditulis

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

Kesalahan

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)

Jawaban:


182

Sederhana saja. Anda mencoba menguji komponen pembungkus yang dihasilkan oleh panggilan connect()(MyPlainComponent). Komponen pembungkus itu mengharapkan memiliki akses ke toko Redux. Biasanya toko itu tersedia sebagai context.store, karena di bagian atas hierarki komponen Anda, Anda akan memiliki <Provider store={myStore} />. Namun, Anda merender komponen yang terhubung dengan sendirinya, tanpa toko, jadi itu membuat kesalahan.

Anda punya beberapa opsi:

  • Buat toko dan render <Provider>komponen di sekitar Anda yang terhubung
  • Buat toko dan langsung berikan sebagai <MyConnectedComponent store={store} />, karena komponen yang terhubung juga akan menerima "toko" sebagai penyangga
  • Jangan repot-repot menguji komponen yang terhubung. Ekspor "polos", versi yang tidak terhubung, dan coba itu. Jika Anda menguji komponen sederhana dan mapStateToPropsfungsi Anda, Anda dapat dengan aman menganggap versi yang terhubung akan berfungsi dengan benar.

Anda mungkin ingin membaca halaman "Pengujian" di dokumen Redux: https://redux.js.org/recipes/writing-tests .

edit :

Setelah benar-benar melihat bahwa Anda memposting sumber, dan membaca kembali pesan kesalahan, masalah sebenarnya bukan pada komponen SportsTopPane. Masalahnya adalah Anda mencoba untuk "sepenuhnya" membuat SportsTopPane, yang juga menjadikan semua anak-anaknya, daripada melakukan render "dangkal" seperti yang Anda lakukan pada kasus pertama. GarissearchComponent = <SportsDatabase sportsWholeFramework="desktop" />; membuat komponen yang saya asumsikan juga terhubung, dan oleh karena itu mengharapkan toko akan tersedia dalam fitur "konteks" Bereaksi.

Pada titik ini, Anda memiliki dua opsi baru:

  • Hanya lakukan rendering "dangkal" dari SportsTopPane, sehingga Anda tidak memaksanya untuk sepenuhnya membuat anak-anaknya
  • Jika Anda ingin melakukan rendering SportsTopPane "dalam", Anda harus menyediakan toko Redux dalam konteksnya. Saya sangat menyarankan Anda melihat perpustakaan pengujian Enzim, yang memungkinkan Anda melakukan hal itu. Lihat http://airbnb.io/enzyme/docs/api/ReactWrapper/setContext.html untuk contoh.

Secara keseluruhan, saya akan mencatat bahwa Anda mungkin mencoba melakukan terlalu banyak dalam komponen yang satu ini dan mungkin ingin mempertimbangkan memecahnya menjadi potongan-potongan kecil dengan sedikit logika per komponen.


Saya mencoba tetapi tidak yakin bagaimana caranya ... dapatkah Anda memperbarui dalam kasus pengujian saya

1
Saya berasumsi bahwa di SportsTopPortion.js, Anda punya let SportsTopPortion = connect(mapStateToProps)(SomeOtherComponent). Jawaban termudah adalah menguji komponen lain itu , bukan komponen yang dikembalikan connect.
markerikson

1
Aha. Sekarang saya melihat apa yang terjadi. Masalahnya bukan dengan SportsTopPane itu sendiri. Masalahnya adalah bahwa Anda melakukan render SportsTopPane "penuh", bukan render "dangkal", jadi React berusaha membuat sepenuhnya seluruh anak. Pesan kesalahan merujuk ke baris searchComponent = <SportsDatabase sportsWholeFramework="desktop" />;. Itu adalah komponen yang terhubung yang mengharapkan toko dan rusak. Jadi, dua saran baru: lakukan saja rendering SportsTopPane yang dangkal, atau gunakan perpustakaan seperti Enzyme untuk menguji. Lihat airbnb.io/enzyme/docs/api/ReactWrapper/setContext.html .
markerikson

dapatkah Anda memberi tahu saya cara menulis test case untuk skenario ini `` `tetapi tidak yakin bagaimana menulis unit test case ketika ponselnya seharusnya tidak membuat komponen nav. `` `

3
Menjawab seseorang yang mandek atau bingung dengan frasa "ini cukup sederhana" bisa dianggap meremehkan atau kasar. Silakan gunakan hemat.
jayqui

97

Kemungkinan solusi yang berhasil bagi saya dengan bercanda

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
berfungsi dengan baik, daripada harus melewati alat peraga satu per satu.
ghostkraviz

2
Terima kasih, solusi yang sangat bagus. Saya punya masalah ini karena saya menggunakan komponen App tingkat atas dengan perutean dan toko disediakan untuk aplikasi anak di setiap rute sehingga saya tidak harus memasukkan alat peraga ke router. Saya mengubahnya sedikit untuk saya gunakan. const wrapper = shallow (<Provider store = {store}> <App /> </Provider>); harapkan (wrapper.contains (<App />)).toBe(true);
Little Brain

69

Seperti yang disarankan oleh dokumen resmi redux, lebih baik untuk mengekspor komponen yang tidak terhubung juga.

Untuk dapat menguji komponen Aplikasi itu sendiri tanpa harus berurusan dengan dekorator, kami sarankan Anda untuk juga mengekspor komponen yang tidak didekorasi:

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)

Karena ekspor default masih merupakan komponen yang dihiasi, pernyataan impor yang digambarkan di atas akan berfungsi seperti sebelumnya sehingga Anda tidak perlu mengubah kode aplikasi Anda. Namun, Anda sekarang dapat mengimpor komponen Aplikasi yang tidak didekorasi dalam file pengujian Anda seperti ini:

// Note the curly braces: grab the named export instead of default export
import { App } from './App'

Dan jika Anda membutuhkan keduanya:

import ConnectedApp, { App } from './App'

Di aplikasi itu sendiri, Anda masih akan mengimpornya secara normal:

import App from './App'

Anda hanya akan menggunakan ekspor yang disebutkan untuk pengujian.


1
Jawaban ini juga sah. Edit tautan Anda untuk mencocokkan anchor.
Erowlin

Jawaban ini masuk akal! Ini mungkin bukan hal yang benar dalam semua kasus, tapi jelas lebih baik daripada bermain dengan Penyedia dan semua itu ketika itu tidak perlu.
lokori

Terima kasih @lokori Senang Anda menyukainya!
Vishal Gulati

2
Ini adalah cara tercepat dan termudah untuk lulus ujian saya lagi.
Mike Lyons

2
"Kamu hanya akan menggunakan ekspor yang disebutkan untuk pengujian." - Bekerja untukku.
technazi

7

Ketika kita menyusun aplikasi react-redux, kita seharusnya melihat struktur di mana di bagian atas kita memiliki Providertag yang memiliki instance dari toko redux.

Itu Providertag kemudian menjadikan komponen orang tua Anda, memungkinkan menyebutnya Appkomponen yang pada gilirannya menjadikan setiap komponen lain di dalam aplikasi.

Inilah bagian kuncinya, ketika kita membungkus komponen dengan connect()fungsi, connect()fungsi itu mengharapkan untuk melihat beberapa komponen induk dalam hierarki yang memiliki Providertag.

Jadi, ketika Anda meletakkan connect()fungsi di sana, itu akan mencari hierarki dan mencoba untuk menemukan Provider.

Itulah yang Anda inginkan terjadi, tetapi di lingkungan pengujian Anda, aliran itu mogok.

Mengapa?

Mengapa?

Ketika kita kembali ke file tes sportsDatabase yang diasumsikan, Anda harus menjadi komponen sportsDatabase dengan sendirinya dan kemudian mencoba membuat komponen itu dengan sendirinya dalam isolasi.

Jadi pada dasarnya apa yang Anda lakukan di dalam file tes hanya mengambil komponen itu dan hanya membuangnya di alam bebas dan tidak memiliki ikatan dengan apa pun Provideratau menyimpan di atasnya dan itulah sebabnya Anda melihat pesan ini.

Tidak ada toko atau Providertag dalam konteks atau penyangga komponen itu dan komponen tersebut membuat kesalahan karena ingin melihat Providertag atau toko dalam hierarki induknya.

Jadi itulah arti kesalahan itu.


6

dalam kasus saya saja

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

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

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


2

hanya melakukan impor ini {dangkal, mount} dari "enzim";

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

Bagi saya itu masalah impor, semoga membantu. Impor default oleh WebStorm salah.

menggantikan

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

dengan

import {connect} from "react-redux";


0

di akhir Index.js Anda perlu menambahkan Kode ini:

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

Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.