Niezmienione naruszenie: nie można znaleźć „sklepu” ani w kontekście, ani w rekwizytach „Connect (SportsDatabase)”


142

Pełny kod tutaj: https://gist.github.com/js08/0ec3d70dfda76d7e9fb4

Cześć,

  • Mam aplikację, w której pokazuje różne szablony dla komputerów stacjonarnych i mobilnych w oparciu o środowisko kompilacji.
  • Z powodzeniem mogę go opracować tam, gdzie muszę ukryć menu nawigacyjne dla mojego szablonu mobilnego.
  • w tej chwili jestem w stanie napisać jeden przypadek testowy, w którym pobiera wszystkie wartości przez proptypes i renderuje się poprawnie
  • ale nie jestem pewien, jak napisać jednostkowe przypadki testowe, gdy jego telefon komórkowy nie powinien renderować komponentu nawigacyjnego.
  • Próbowałem, ale napotykam błąd ... czy możesz mi powiedzieć, jak to naprawić.
  • Dowód kodu poniżej.

Przypadek testowy

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

});

Fragment kodu, w którym należy zapisać przypadek testowy

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

Błąd

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)

Odpowiedzi:


182

To całkiem proste. Próbujesz przetestować komponent otoki wygenerowany przez wywołanie connect()(MyPlainComponent). Ten komponent opakowania oczekuje, że będzie miał dostęp do sklepu Redux. Zwykle ten sklep jest dostępny jako context.store, ponieważ na szczycie hierarchii komponentów miałbyś plik <Provider store={myStore} />. Jednak renderujesz podłączony komponent samodzielnie, bez sklepu, więc generuje błąd.

Masz kilka opcji:

  • Utwórz sklep i wyrenderuj <Provider>wokół podłączonego komponentu
  • Utwórz sklep i przekaż go bezpośrednio jako <MyConnectedComponent store={store} />, ponieważ połączony komponent również zaakceptuje „sklep” jako rekwizyt
  • Nie przejmuj się testowaniem podłączonego komponentu. Wyeksportuj „zwykłą”, niepołączoną wersję i przetestuj ją. Jeśli przetestujesz swój zwykły komponent i swoją mapStateToPropsfunkcję, możesz bezpiecznie założyć, że podłączona wersja będzie działać poprawnie.

Prawdopodobnie chcesz przeczytać stronę „Testowanie” w dokumentacji Redux: https://redux.js.org/recipes/writing-tests .

edytuj :

Po zobaczeniu, że opublikowałeś źródło i ponownym przeczytaniu komunikatu o błędzie, prawdziwy problem nie dotyczy składnika SportsTopPane. Problem polega na tym, że próbujesz „w pełni” wyrenderować SportsTopPane, który również renderuje wszystkie swoje elementy podrzędne, zamiast wykonywać „płytki” render, tak jak w pierwszym przypadku. Linia searchComponent = <SportsDatabase sportsWholeFramework="desktop" />;renderuje komponent, który, jak zakładam, jest również połączony, i dlatego oczekuje, że sklep będzie dostępny w funkcji „kontekst” Reacta.

W tym momencie masz dwie nowe opcje:

  • Rób tylko „płytkie” renderowanie SportsTopPane, aby nie zmuszać go do pełnego renderowania elementów podrzędnych
  • Jeśli chcesz wykonać „głębokie” renderowanie SportsTopPane, musisz podać sklep Redux w kontekście. Gorąco radzę zajrzeć do biblioteki testów enzymów, która pozwala dokładnie to zrobić. Zobacz przykład http://airbnb.io/enzyme/docs/api/ReactWrapper/setContext.html .

Ogólnie rzecz biorąc, chciałbym zauważyć, że możesz próbować zrobić zbyt dużo w tym jednym komponencie i możesz rozważyć podzielenie go na mniejsze części z mniejszą logiką na komponent.


Próbowałem, ale nie jestem pewien, jak to zrobić ... czy możesz zaktualizować moje przypadki testowe

1
Zakładam, że w SportsTopPortion.js masz let SportsTopPortion = connect(mapStateToProps)(SomeOtherComponent). Najłatwiejszą odpowiedzią jest przetestowanie tego innego komponentu, a nie komponentu zwracanego przezconnect .
markerikson

1
Aha. Teraz widzę, co się dzieje. Problem nie dotyczy samego SportsTopPane. Problem polega na tym, że wykonujesz „pełny” render SportsTopPane, a nie „płytki” render, więc React próbuje w pełni wyrenderować wszystkie dzieci. Komunikat o błędzie odnosi się do linii searchComponent = <SportsDatabase sportsWholeFramework="desktop" />;. To jest połączony komponent, który oczekuje sklepu i się psuje. Tak więc dwie nowe sugestie: albo wykonaj tylko płytkie renderowanie SportsTopPane, albo użyj biblioteki takiej jak Enzyme do przetestowania. Zobacz airbnb.io/enzyme/docs/api/ReactWrapper/setContext.html .
markerikson

czy możesz mi powiedzieć, jak napisać przypadek testowy dla tego scenariusza ``, ale nie jestem pewien, jak napisać przypadki testów jednostkowych, gdy jego telefon komórkowy nie powinien renderować komponentu nawigacyjnego. `` `

3
Udzielenie odpowiedzi komuś, kto utknął lub jest zdziwiony frazą „to całkiem proste”, może wydawać się lekceważące lub szorstkie. Proszę używać oszczędnie.
jayqui

97

Możliwe rozwiązanie, które zadziałało dla mnie żartem

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
działa dobrze, zamiast przesyłać rekwizyty jeden po drugim.
ghostkraviz

2
Dziękuję bardzo fajne rozwiązanie. Miałem ten problem, ponieważ używam komponentu aplikacji najwyższego poziomu z routingiem, a sklep jest dostarczany do aplikacji podrzędnej na każdej trasie, więc nie muszę przekazywać rekwizytów do routera. Zmieniłem to trochę na swój użytek. const wrapper = shallow (<Provider store = {store}> <App /> </Provider>); oczekiwać (wrapper.contains (<App />)).toBe(true);
Little Brain

69

Jak oficjalna dokumentacja sugerują Redux, lepiej wyeksportować również niepołączony komponent.

Aby móc przetestować sam komponent aplikacji bez konieczności zajmowania się dekoratorem, zalecamy wyeksportowanie również niezdekorowanego komponentu:

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)

Ponieważ domyślnym eksportem jest nadal dekorowany komponent, instrukcja importu przedstawiona powyżej będzie działać jak poprzednio, więc nie będziesz musiał zmieniać kodu aplikacji. Możesz jednak teraz importować niezdekorowane komponenty aplikacji do pliku testowego w następujący sposób:

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

A jeśli potrzebujesz obu:

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

W samej aplikacji nadal normalnie importujesz ją:

import App from './App'

Nazwany eksport używałbyś tylko do testów.


1
Ta odpowiedź jest również uzasadniona. Edytowano link, aby pasował do kotwicy.
Erowlin

Ta odpowiedź ma sens! Może nie jest to właściwe rozwiązanie we wszystkich przypadkach, ale zdecydowanie lepsze niż zabawa z Dostawcą i to wszystko, gdy nie jest to konieczne.
lokori

Dzięki @lokori. Szczęśliwy, że ci się podoba!
Vishal Gulati

2
To był najszybszy i najprostszy sposób na ponowne zdanie testu.
Mike Lyons

2
„Nazwany eksport używałbyś tylko do testów”. -- Pracuje dla mnie.
technazi

7

Kiedy tworzymy aplikację react-redux, powinniśmy spodziewać się struktury, w której u góry mamy Providertag, który zawiera instancję sklepu redux.

Ten Providertag następnie renderuje komponent nadrzędny, nazwijmy go Appkomponentem, który z kolei renderuje każdy inny komponent wewnątrz aplikacji.

Oto kluczowa część, kiedy opakowujemy komponent connect()funkcją, ta connect()funkcja oczekuje , że zobaczy jakiś komponent nadrzędny w hierarchii, która ma Providerznacznik.

Więc instancja, w której umieścisz connect()funkcję, przeszuka hierarchię i spróbuje znaleźćProvider .

To jest to, co chcesz, aby się wydarzyło, ale w twoim środowisku testowym ten przepływ się psuje.

Czemu?

Czemu?

Kiedy wracamy do zakładanego pliku testowego sportsDatabase, musisz sam być komponentem sportsDatabase, a następnie próbować renderować ten komponent samodzielnie w izolacji.

Więc zasadniczo to, co robisz w tym pliku testowym, to po prostu pobranie tego komponentu i po prostu wyrzucenie go na wolność i nie ma on żadnych powiązań z żadnym Providerlub magazynem nad nim i dlatego widzisz tę wiadomość.

W Providerkontekście lub właściwości tego komponentu nie ma magazynu ani tagu, więc komponent zgłasza błąd, ponieważ chce zobaczyć Providertag lub przechowywać w swojej nadrzędnej hierarchii.

Więc to właśnie oznacza ten błąd.


6

w moim przypadku po prostu

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

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

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


2

po prostu zrób to importuj {płytko, góra} z "enzymu";

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

Dla mnie był to problem z importem, mam nadzieję, że pomoże. domyślny import przez WebStorm był nieprawidłowy.

zastąpić

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

z

import {connect} from "react-redux";


0

na końcu pliku Index.js należy dodać ten kod:

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

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.