Інваріантне порушення: Не вдалося знайти "зберігати" ні в контексті, ні в реквізиті "Підключення (SportsDatabase)"


142

Повний код тут: https://gist.github.com/js08/0ec3d70dfda76d7e9fb4

Привіт,

  • У мене є додаток, де він показує різні шаблони для настільних і мобільних на основі середовища побудови.
  • Я успішно в змозі розробити його там, де мені потрібно приховати меню навігації для свого шаблону для мобільних пристроїв.
  • зараз я можу написати один тестовий випадок, коли він отримує всі значення через пропіти та візуалізує правильно
  • але не впевнений, як писати одиничні тестові випадки, коли його мобільний не повинен відображати 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)

Відповіді:


182

Це досить просто. Ви намагаєтеся перевірити компонент обгортки, згенерований дзвінками connect()(MyPlainComponent). Цей компонент обгортки очікує мати доступ до магазину Redux. Зазвичай цей магазин доступний як context.store, тому що у верхній частині ієрархії компонентів у вас є <Provider store={myStore} />. Однак ви надаєте підключений компонент сам, без магазину, тому це призводить до помилки.

У вас є кілька варіантів:

  • Створіть магазин і візьміть <Provider>навколо свого підключеного компонента
  • Створіть магазин і безпосередньо передайте його як <MyConnectedComponent store={store} />, оскільки підключений компонент також прийме "зберігати" як опору
  • Не переймайтесь тестуванням підключеного компонента. Експортуйте "звичайну", непоєднану версію, і замість цього протестуйте. Якщо ви перевірите звичайний компонент і свою mapStateToPropsфункцію, можете сміливо припускати, що підключена версія буде працювати правильно.

Ймовірно, ви хочете прочитати сторінку "Тестування" в документах Redux: https://redux.js.org/recipes/writing-tests .

редагувати :

Після того, як ви фактично побачили, що ви розмістили джерело, і перечитали повідомлення про помилку, справжня проблема полягає не в компоненті SportsTopPane. Проблема полягає в тому, що ви намагаєтесь "повноцінно" відобразити SportsTopPane, який також робить усіх його дітей, а не робити "неглибокий" рендер, як ви були у першому випадку. Рядок searchComponent = <SportsDatabase sportsWholeFramework="desktop" />;відображає компонент, який, я вважаю, також підключений, і тому очікує, що магазин буде доступний в "контекстній" функції React.

На даний момент у вас є два нові варіанти:

  • Робіть лише "неглибоке" візуалізацію SportsTopPane, щоб ви не змушували його повністю виводити своїх дітей
  • Якщо ви хочете зробити "глибоке" візуалізацію SportsTopPane, вам потрібно надати магазин Redux в контексті. Я настійно пропоную вам поглянути на бібліотеку тестування ферментів, яка дозволяє вам робити саме це. Для прикладу див. Http://airbnb.io/enzyme/docs/api/ReactWrapper/setContext.html .

В цілому, я зауважу, що ви, можливо, намагаєтеся зробити надто багато в цьому одному компоненті, і, можливо, ви хочете розглянути можливість його розбиття на більш дрібні частини з меншою логікою на компонент.


Я спробував, але не впевнений, як це зробити ... чи можете ви оновити в моїх тестових випадках

1
Я припускаю, що у SportsTopPortion.js у вас є let SportsTopPortion = connect(mapStateToProps)(SomeOtherComponent). Найпростіша відповідь - перевірити той інший компонент, а не той компонент, який повертається connect.
markerikson

1
Ага. Тепер я бачу, що відбувається. Проблема не в самому SportsTopPane. Проблема полягає в тому, що ви робите "повний" візуалізацію SportsTopPane, а не "неглибоку", тому React намагається повністю вивести всіх дітей. Повідомлення про помилку стосується рядка searchComponent = <SportsDatabase sportsWholeFramework="desktop" />;. Це підключений компонент, який очікує, що магазин і зламається. Отож, дві нові пропозиції: або робити лише неглибоке відображення SportsTopPane, або використовувати бібліотеку типу Enzyme для тестування. Див. Airbnb.io/enzyme/docs/api/ReactWrapper/setContext.html .
markerikson

чи можете ви сказати мені, як написати тестовий випадок для цього сценарію `` `, але не знаєте, як писати тестові випадки пристрою, коли його мобільний не повинен відображати компонент nav. `` `

3
Відповідь того, хто застряг або спантеличений фразою "це досить просто", може виявитися зневажливим або суворим. Будь ласка, використовуйте економно.
jayqui

97

Можливе рішення, яке працювало на мене жартома

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
працює добре, замість того, щоб передавати реквізити один за одним.
ghostkraviz

2
Дякую, дуже приємне рішення. У мене виникла ця проблема, оскільки я використовую компонент додатків верхнього рівня з маршрутизацією, і магазин надається дочірньому додатку в кожному маршруті, тому мені не потрібно передавати реквізити в маршрутизатор. Я трохи змінив це для мого використання. const wrapper = неглибокий (<Магазин постачальника = {магазин}> <Додаток /> </Provider>); очікувати (wrapper.contains (<App />)).toBe(true);
Маленький мозок

69

Як підказують офіційні документи редукції, краще експортувати і непоєднаний компонент.

Щоб мати можливість протестувати сам компонент програми, не маючи справи з декоратором, радимо також експортувати незадекларований компонент:

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)

Оскільки експорт за замовчуванням все ще є декорованим компонентом, представлений вище імпорт імпорту буде працювати як раніше, тому вам не доведеться змінювати код програми. Однак тепер ви можете імпортувати незабарвлені компоненти програми у свій тестовий файл таким чином:

// 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'

Ви б використовували названий експорт лише для тестів.


1
Ця відповідь також законна. Відредагував своє посилання, щоб відповідати якоря.
Еровлін

Ця відповідь має ідеальний сенс! Це може бути не правильним у всіх випадках, але, безумовно, краще, ніж грати з Постачальником і все, коли це не потрібно.
lokori

Дякую @lokori Щасливий вам це подобається!
Вішал Гулаті

2
Це був найшвидший і найпростіший спосіб знову пройти тест.
Майк Ліонс

2
"Ви б використовували названий експорт лише для тестів." - Для мене працює.
технозі

7

Коли ми збираємо додаток react-redux, нам слід очікувати, що ми побачимо структуру, де вгорі у нас є Providerтег, який містить екземпляр магазину redux.

ProviderПотім цей тег надає ваш батьківський компонент, дозволяє називати його Appкомпонентом, який, у свою чергу, робить кожен інший компонент у програмі.

Ось ключова частина, коли ми завершуємо компонент з connect()функцією, ця connect()функція очікує побачити якийсь батьківський компонент в ієрархії, що містить Providerтег.

Отже, екземпляр, який ви помістили connect()туди, перегляне ієрархію та спробує знайти її Provider.

Це те, що ви хочете, щоб сталося, але у вашому тестовому середовищі потік руйнується.

Чому?

Чому?

Коли ми повернемося до передбачуваного тестового файлу sportsDatabase, ви повинні бути компонентом sportsDatabase сам по собі, а потім намагаєтесь зробити цей компонент самостійно.

Отже, по суті, те, що ви робите всередині цього тестового файлу, - це просто взяти цей компонент і просто викинути його в дикій природі, і він не має зв'язку ні з одним, Providerні з магазином над ним, і ось чому ви бачите це повідомлення.

У Providerконтексті чи опорі цього компонента немає зберігання чи тегів, і тому компонент видає помилку, оскільки хоче бачити Providerтег чи зберігати у своїй батьківській ієрархії.

Отже, це означає, що ця помилка.


6

в моєму випадку просто

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

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

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


2

jus виконують цей імпорт {shallow, mount} з "ферменту";

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

Для мене це питання імпорту, сподіваюся, що це допомагає. імпорт за замовчуванням WebStorm був помилковим.

замінити

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

з

import {connect} from "react-redux";

1

Це сталося зі мною, коли я модернізував. Мені довелося повернути назад.

реакція-редукція ^ 5.0.6 → ^ 7.1.3


Це скоріше коментар, ніж відповідь
sudo97

Було багато переломних змін. Рекомендую переглянути це відео, щоб краще зрозуміти зміни youtube.com/watch?v=yOZ4Ml9LlWE
Kamil Dzieniszewski

0

в кінці вашого Index.js потрібно додати цей Код:

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

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.