Інваріантне порушення: Не вдалося знайти "зберігати" ні в контексті, ні в реквізиті "Підключення (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.