Invariante Verletzung: "Store" konnte weder im Kontext noch in den Requisiten von "Connect (SportsDatabase)" gefunden werden.

142

Vollständiger Code hier: https://gist.github.com/js08/0ec3d70dfda76d7e9fb4

Hallo,

  • Ich habe eine Anwendung, in der verschiedene Vorlagen für Desktop und Mobile basierend auf der Build-Umgebung angezeigt werden.
  • Ich kann es erfolgreich dort entwickeln, wo ich das Navigationsmenü für meine mobile Vorlage ausblenden muss.
  • Im Moment kann ich einen Testfall schreiben, in dem alle Werte durch die Proptypen abgerufen und korrekt gerendert werden
  • aber nicht sicher, wie man die Unit-Testfälle schreibt, wenn sein Handy keine Navi-Komponente rendern soll.
  • Ich habe es versucht, aber ich habe einen Fehler festgestellt. Können Sie mir sagen, wie ich ihn beheben kann?
  • Testcode unten.

Testfall

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

});

Code-Snippet, in das der Testfall geschrieben werden muss

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

Error

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)

quelle

Antworten:

182

Es ist ziemlich einfach. Sie versuchen, die durch Aufruf generierte Wrapper-Komponente zu testen connect()(MyPlainComponent). Diese Wrapper-Komponente erwartet Zugriff auf einen Redux-Speicher. Normalerweise ist dieser Speicher als verfügbar context.store, da Sie oben in Ihrer Komponentenhierarchie eine haben würden <Provider store={myStore} />. Sie rendern Ihre verbundene Komponente jedoch selbst, ohne Speicher, sodass ein Fehler ausgegeben wird.

Sie haben einige Möglichkeiten:

  • Erstellen Sie ein Geschäft und rendern Sie ein <Provider>um Ihre verbundene Komponente
  • Erstellen Sie ein Geschäft und übergeben Sie es direkt als <MyConnectedComponent store={store} />, da die verbundene Komponente auch "Geschäft" als Requisite akzeptiert
  • Testen Sie die angeschlossene Komponente nicht. Exportieren Sie die "einfache", nicht verbundene Version und testen Sie diese stattdessen. Wenn Sie Ihre einfache Komponente und Ihre mapStateToPropsFunktion testen , können Sie davon ausgehen, dass die angeschlossene Version ordnungsgemäß funktioniert.

Sie möchten wahrscheinlich die Seite "Testen" in den Redux-Dokumenten lesen: https://redux.js.org/recipes/writing-tests .

bearbeiten :

Nachdem Sie tatsächlich gesehen haben, dass Sie die Quelle gepostet haben, und die Fehlermeldung erneut gelesen haben, liegt das eigentliche Problem nicht bei der SportsTopPane-Komponente. Das Problem ist, dass Sie versuchen, SportsTopPane "vollständig" zu rendern, wodurch auch alle untergeordneten Elemente gerendert werden, anstatt wie im ersten Fall ein "flaches" Rendern durchzuführen. Die Zeile searchComponent = <SportsDatabase sportsWholeFramework="desktop" />;rendert eine Komponente, von der ich annehme, dass sie auch verbunden ist, und erwartet daher, dass ein Speicher in der "Kontext" -Funktion von React verfügbar ist.

Zu diesem Zeitpunkt haben Sie zwei neue Optionen:

  • Führen Sie das "flache" Rendern von SportsTopPane nur durch, damit Sie es nicht zwingen, seine untergeordneten Elemente vollständig zu rendern
  • Wenn Sie SportsTopPane "tief" rendern möchten, müssen Sie im Kontext einen Redux-Speicher bereitstellen. Ich empfehle Ihnen dringend, einen Blick auf die Enzymtestbibliothek zu werfen, mit der Sie genau das tun können. Ein Beispiel finden Sie unter http://airbnb.io/enzyme/docs/api/ReactWrapper/setContext.html .

Insgesamt würde ich bemerken, dass Sie möglicherweise versuchen, zu viel in dieser einen Komponente zu tun, und möglicherweise in Betracht ziehen möchten, sie mit weniger Logik pro Komponente in kleinere Teile zu zerlegen.

markerikson
quelle
Ich habe versucht, aber nicht sicher, wie es geht ... können Sie in meinen Testfällen aktualisieren
1
Ich gehe davon aus, dass Sie in SportsTopPortion.js haben let SportsTopPortion = connect(mapStateToProps)(SomeOtherComponent). Die einfachste Antwort besteht darin, diese andere Komponente zu testen , nicht die von zurückgegebene Komponente connect.
markerikson
1
Aha. Jetzt sehe ich, was passiert. Das Problem liegt nicht bei SportsTopPane selbst. Das Problem ist, dass Sie ein "vollständiges" Rendern von SportsTopPane ausführen, kein "flaches" Rendern. Daher versucht React, alle untergeordneten Elemente vollständig zu rendern. Die Fehlermeldung bezieht sich auf die Zeile searchComponent = <SportsDatabase sportsWholeFramework="desktop" />;. Das ist die verbundene Komponente, die ein Geschäft erwartet und bricht. Also zwei neue Vorschläge: entweder nur flaches Rendern von SportsTopPane oder Verwenden einer Bibliothek wie Enzyme zum Testen. Siehe airbnb.io/enzyme/docs/api/ReactWrapper/setContext.html .
Markerikson
Können Sie mir sagen, wie der Testfall für dieses Szenario geschrieben werden soll? `` `
3
Die Beantwortung von Personen, die mit dem Satz "Es ist ziemlich einfach" festgefahren oder verwirrt sind, kann als abwertend oder hart empfunden werden. Bitte sparsam verwenden.
Jayqui
97

Mögliche Lösung, die bei mir mit Scherz funktioniert hat

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);
    });
});
Codeislife
quelle
1
funktioniert gut, anstatt Requisiten einzeln übergeben zu müssen.
Ghostkraviz
2
Vielen Dank, eine sehr schöne Lösung. Ich hatte dieses Problem, weil ich eine App-Komponente der obersten Ebene mit Routing verwende und der Speicher der untergeordneten App auf jeder Route zur Verfügung gestellt wird, sodass ich keine Requisiten an den Router übergeben muss. Ich habe es ein wenig für meinen Gebrauch geändert. const wrapper = flach (<Provider store = {store}> <App /> </ Provider>); erwarten (wrapper.contains (<App />)).toBe(true);
Little Brain
69

Wie aus den offiziellen Dokumenten von Redux hervorgeht, ist es besser, auch die nicht verbundene Komponente zu exportieren.

Um die App-Komponente selbst testen zu können, ohne sich um den Dekorateur kümmern zu müssen, empfehlen wir Ihnen, auch die nicht dekorierte Komponente zu exportieren:

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)

Da der Standardexport immer noch die dekorierte Komponente ist, funktioniert die oben abgebildete Importanweisung wie zuvor, sodass Sie Ihren Anwendungscode nicht ändern müssen. Sie können die nicht dekorierten App-Komponenten jetzt jedoch wie folgt in Ihre Testdatei importieren:

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

Und wenn Sie beides brauchen:

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

In der App selbst würden Sie sie weiterhin normal importieren:

import App from './App'

Sie würden den genannten Export nur für Tests verwenden.

Vishal Gulati
quelle
1
Diese Antwort ist auch legitim. Bearbeiten Sie Ihren Link so, dass er mit dem Anker übereinstimmt.
Erowlin
Diese Antwort macht vollkommen Sinn! Es ist vielleicht nicht in allen Fällen das Richtige, aber definitiv besser als mit dem Provider zu spielen und all das, wenn es nicht notwendig ist.
Lokori
Danke @lokori Schön, dass es dir gefällt!
Vishal Gulati
2
Dies war der schnellste und einfachste Weg, um meinen Test wieder zu bestehen.
Mike Lyons
2
"Sie würden den genannten Export nur für Tests verwenden." -- Funktioniert bei mir.
Technazi
7

Wenn wir eine React-Redux-Anwendung zusammenstellen, sollten wir eine Struktur erwarten, in der wir oben das ProviderTag haben, das eine Instanz eines Redux-Speichers enthält.

Dieses ProviderTag rendert dann Ihre übergeordnete Komponente. Nennen wir es die AppKomponente, die wiederum jede andere Komponente in der Anwendung rendert.

Hier ist der Schlüsselteil: Wenn wir eine Komponente mit der connect()Funktion connect()umschließen, erwartet diese Funktion, dass eine übergeordnete Komponente in der Hierarchie mit dem ProviderTag angezeigt wird.

Wenn Sie also die connect()Funktion dort einfügen, wird die Hierarchie nachgeschlagen und versucht, die zu finden Provider.

Das ist, was Sie wollen, aber in Ihrer Testumgebung bricht dieser Fluss zusammen.

Warum?

Warum?

Wenn wir zur angenommenen sportsDatabase-Testdatei zurückkehren, müssen Sie die sportsDatabase-Komponente für sich sein und dann versuchen, diese Komponente für sich isoliert zu rendern.

Was Sie also in dieser Testdatei tun, ist im Wesentlichen, diese Komponente zu nehmen und sie einfach in der Wildnis Providerwegzuwerfen, und sie hat keine Verbindungen zu irgendwelchen oder Speichern darüber, und deshalb sehen Sie diese Nachricht.

ProviderDer Kontext oder die Requisite dieser Komponente enthält kein Speichern oder Tag. Daher gibt die Komponente einen Fehler aus, da sie ein ProviderTag oder einen Speicher in ihrer übergeordneten Hierarchie anzeigen möchte .

Das ist es also, was dieser Fehler bedeutet.

Daniel
quelle
6

in meinem Fall nur

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

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

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

jose920405
quelle
2

jus mache diesen Import {flach, montiert} von "Enzym";

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

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

  let wrapper;
  beforeEach(() => {
    wrapper = setUpFn();
  });
Hilal Aissani
quelle
2

Für mich war es ein Importproblem, hoffe es hilft. Der Standardimport von WebStorm war falsch.

ersetzen

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

mit

import {connect} from "react-redux";
ATQSHL
quelle
1

Dies ist mir beim Upgrade passiert. Ich musste zurückstufen.

React-Redux ^ 5.0.6 → ^ 7.1.3

Codie
quelle
Dies ist eher ein Kommentar als ein Asnwer
sudo97
Es gab viele bahnbrechende Veränderungen. Ich empfehle, dieses Video anzuschauen, um die Änderungen besser zu verstehen Youtube.com/watch?v=yOZ4Ml9LlWE
Kamil Dzieniszewski
0

Am Ende Ihres Index.js müssen Sie diesen Code hinzufügen:

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

Mehrdad
quelle