Violation invariante: impossible de trouver «magasin» dans le contexte ou dans les accessoires de «Connect (SportsDatabase)»

142

Code complet ici: https://gist.github.com/js08/0ec3d70dfda76d7e9fb4

Salut,

  • J'ai une application où il montre différents modèles pour ordinateur de bureau et mobile en fonction de l'environnement de construction.
  • Je parviens à le développer là où je dois masquer le menu de navigation de mon modèle mobile.
  • en ce moment, je suis capable d'écrire un cas de test où il récupère toutes les valeurs via les proptypes et rend correctement
  • mais je ne sais pas comment écrire les cas de test unitaires lorsque son mobile, il ne doit pas rendre le composant de navigation.
  • J'ai essayé mais je suis confronté à une erreur ... pouvez-vous me dire comment y remédier.
  • code de preuve ci-dessous.

Cas de test

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

});

Extrait de code où le cas de test doit être écrit

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

Erreur

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)

la source

Réponses:

182

C'est assez simple. Vous essayez de tester le composant wrapper généré en appelant connect()(MyPlainComponent). Ce composant wrapper s'attend à avoir accès à un magasin Redux. Normalement, ce magasin est disponible en tant que context.store, car en haut de votre hiérarchie de composants, vous auriez un fichier <Provider store={myStore} />. Cependant, vous effectuez le rendu de votre composant connecté par lui-même, sans magasin, il génère donc une erreur.

Vous avez plusieurs options:

  • Créez un magasin et effectuez un rendu <Provider>autour de votre composant connecté
  • Créez un magasin et passez-le directement en tant que <MyConnectedComponent store={store} />, car le composant connecté acceptera également le "magasin" comme accessoire
  • Ne prenez pas la peine de tester le composant connecté. Exportez la version "simple", non connectée, et testez-la à la place. Si vous testez votre composant ordinaire et votre mapStateToPropsfonction, vous pouvez supposer que la version connectée fonctionnera correctement.

Vous voudrez probablement lire la page "Test" dans la documentation Redux: https://redux.js.org/recipes/writing-tests .

modifier :

Après avoir vu que vous avez publié la source et relu le message d'erreur, le vrai problème ne vient pas du composant SportsTopPane. Le problème est que vous essayez de rendre "entièrement" SportsTopPane, qui rend également tous ses enfants, plutôt que de faire un rendu "superficiel" comme vous l'étiez dans le premier cas. La ligne searchComponent = <SportsDatabase sportsWholeFramework="desktop" />;rend un composant qui, je suppose, est également connecté, et s'attend donc à ce qu'un magasin soit disponible dans la fonction «context» de React.

À ce stade, vous avez deux nouvelles options:

  • Effectuez uniquement un rendu "superficiel" de SportsTopPane, afin de ne pas le forcer à rendre entièrement ses enfants
  • Si vous voulez faire un rendu "profond" de SportsTopPane, vous devrez fournir un magasin Redux en contexte. Je vous suggère fortement de jeter un œil à la bibliothèque de test Enzyme, qui vous permet de faire exactement cela. Voir http://airbnb.io/enzyme/docs/api/ReactWrapper/setContext.html pour un exemple.

Dans l'ensemble, je noterais que vous essayez peut-être d'en faire trop dans ce seul composant et que vous voudrez peut-être envisager de le diviser en plus petits morceaux avec moins de logique par composant.

markerikson
la source
J'ai essayé mais je ne sais pas comment faire ... pouvez-vous mettre à jour dans mes cas de test
1
Je suppose que dans SportsTopPortion.js, vous avez let SportsTopPortion = connect(mapStateToProps)(SomeOtherComponent). La réponse la plus simple est de tester cet autre composant, pas le composant renvoyé par connect.
markerikson
1
Aha. Maintenant je vois ce qui se passe. Le problème ne vient pas de SportsTopPane lui-même. Le problème est que vous faites un rendu "complet" de SportsTopPane, pas un rendu "superficiel", donc React essaie de rendre complètement tous les enfants. Le message d'erreur fait référence à la ligne searchComponent = <SportsDatabase sportsWholeFramework="desktop" />;. C'est le composant connecté qui attend un magasin et une rupture. Donc, deux nouvelles suggestions: soit ne faites que le rendu superficiel de SportsTopPane, soit utilisez une bibliothèque comme Enzyme pour tester. Voir airbnb.io/enzyme/docs/api/ReactWrapper/setContext.html .
markerikson
pouvez-vous me dire comment écrire le cas de test pour ce scénario `` `` mais je ne sais pas comment écrire les cas de test unitaire lorsque son mobile, il ne doit pas rendre le composant nav. ``
3
Répondre à quelqu'un qui est coincé ou perplexe avec l'expression «c'est assez simple» peut sembler désobligeant ou dur. Veuillez utiliser avec parcimonie.
jayqui
97

Solution possible qui a fonctionné pour moi avec plaisanterie

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);
    });
});
code vie
la source
1
fonctionne bien, au lieu d'avoir à passer les accessoires un par un.
ghostkraviz
2
Merci, une très belle solution. J'ai eu ce problème parce que j'utilise un composant d'application de haut niveau avec routage et que le magasin est fourni à l'application enfant dans chaque itinéraire, donc je n'ai pas à passer d'accessoires dans le routeur. Je l'ai un peu changé pour mon usage. const wrapper = shallow (<Provider store = {store}> <App /> </Provider>); expect (wrapper.contains (<App />)).toBe(true);
Little Brain
69

Comme le suggèrent les documents officiels de redux, mieux vaut également exporter le composant non connecté.

Afin de pouvoir tester le composant App lui-même sans avoir à traiter avec le décorateur, nous vous recommandons d'exporter également le composant non décoré:

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)

Étant donné que l'exportation par défaut est toujours le composant décoré, l'instruction d'importation illustrée ci-dessus fonctionnera comme avant, vous n'aurez donc pas à modifier le code de votre application. Cependant, vous pouvez maintenant importer les composants d'application non décorés dans votre fichier de test comme ceci:

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

Et si vous avez besoin des deux:

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

Dans l'application elle-même, vous l'importeriez toujours normalement:

import App from './App'

Vous utiliseriez uniquement l'exportation nommée pour les tests.

Vishal Gulati
la source
1
Cette réponse est également légitime. Modification de votre lien pour correspondre à l'ancre.
Erowlin
Cette réponse est parfaitement logique! Ce n'est peut-être pas la bonne chose dans tous les cas, mais certainement mieux que de jouer avec le fournisseur et tout cela quand ce n'est pas nécessaire.
lokori
Merci @lokori Heureux que vous l'aimiez!
Vishal Gulati
2
C'était le moyen le plus rapide et le plus simple de réussir à nouveau mon test.
Mike Lyons
2
"Vous utiliseriez uniquement l'exportation nommée pour les tests." -- Travaille pour moi.
technazi
7

Lorsque nous mettons en place une application react-redux, nous devrions nous attendre à voir une structure où en haut nous avons la Providerbalise qui a une instance d'un magasin redux.

Cette Providerbalise rend ensuite votre composant parent, appelons-le le Appcomposant qui à son tour rend tous les autres composants à l'intérieur de l'application.

Voici la partie clé, lorsque nous enveloppons un composant avec la connect()fonction, cette connect()fonction s'attend à voir un composant parent dans la hiérarchie qui a la Providerbalise.

Ainsi, l'instance dans laquelle vous mettez la connect()fonction là-dedans, elle recherchera la hiérarchie et essaiera de trouver leProvider .

C'est ce que vous voulez faire, mais dans votre environnement de test, ce flux est en panne.

Pourquoi?

Pourquoi?

Lorsque nous revenons au fichier de test de sportsDatabase supposé, vous devez être le composant sportsDatabase par lui-même et ensuite essayer de rendre ce composant par lui-même de manière isolée.

Donc, essentiellement, ce que vous faites à l'intérieur de ce fichier de test consiste simplement à prendre ce composant et à le jeter dans la nature et il n'a aucun lien avec l'un Providerou le stockage au-dessus et c'est pourquoi vous voyez ce message.

Il n'y a pas de magasin ou de Providerbalise dans le contexte ou l'accessoire de ce composant et le composant renvoie donc une erreur car il souhaite voir une Providerbalise ou un magasin dans sa hiérarchie parent.

Voilà donc ce que signifie cette erreur.

Daniel
la source
6

dans mon cas juste

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

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

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

jose920405
la source
2

juste faire cette importation {shallow, mount} depuis "enzyme";

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
la source
2

Pour moi, c'était un problème d'importation, j'espère que cela aide. L'importation par défaut par WebStorm était incorrecte.

remplacer

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

avec

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

Cela m'est arrivé lors de ma mise à niveau. J'ai dû rétrograder.

react-redux ^ 5.0.6 → ^ 7.1.3

codie
la source
Ceci est plus un commentaire qu'une réponse
sudo97
Il y a eu beaucoup de changements de rupture. Je recommande de regarder cette vidéo pour mieux comprendre les changements youtube.com/watch?v=yOZ4Ml9LlWE
Kamil Dzieniszewski
0

à la fin de votre Index.js devez ajouter ce code:

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
la source