Неизменяемое нарушение: не удалось найти «store» ни в контексте, ни в свойствах «Connect (SportsDatabase)».

147

Полный код здесь: https://gist.github.com/js08/0ec3d70dfda76d7e9fb4

Здравствуй,

  • У меня есть приложение, в котором показаны разные шаблоны для настольных и мобильных устройств в зависимости от среды сборки.
  • Я успешно могу разработать его там, где мне нужно скрыть меню навигации для моего мобильного шаблона.
  • прямо сейчас я могу написать один тестовый пример, в котором он извлекает все значения через proptypes и правильно отображает
  • но не уверен, как писать примеры модульного тестирования, когда его мобильный телефон не должен отображать компонент навигации.
  • Я пробовал, но у меня возникла ошибка ... Подскажите, как ее исправить.
  • код ниже.

Прецедент

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)

источник

Ответы:

196

Все очень просто. Вы пытаетесь протестировать компонент оболочки, созданный с помощью вызова connect()(MyPlainComponent). Этот компонент-оболочка ожидает доступа к хранилищу Redux. Обычно это хранилище доступно как context.store, потому что наверху иерархии компонентов у вас будет файл <Provider store={myStore} />. Однако вы визуализируете подключенный компонент отдельно, без хранилища, поэтому он выдает ошибку.

У вас есть несколько вариантов:

  • Создайте магазин и визуализируйте его <Provider>вокруг подключенного компонента
  • Создайте магазин и напрямую передайте его как <MyConnectedComponent store={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
Можете ли вы сказать мне, как написать тестовый пример для этого сценария, но не знаете, как писать модульные тестовые примеры, когда его мобильный телефон не должен отображать компонент навигации. `` `
3
Ответить кому-то, кто застрял или озадачен фразой «это довольно просто», может показаться пренебрежительным или резким. Пожалуйста, используйте экономно.
jayqui
103

Возможное решение, которое сработало для меня в шутку

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 = shallow (<Provider store = {store}> <App /> </Provider>); ожидать (wrapper.contains (<App />)).toBe(true);
Little Brain
71

Как предлагают официальные документы redux, лучше экспортировать и неподключенный компонент.

Чтобы иметь возможность протестировать сам компонент приложения без необходимости иметь дело с декоратором, мы рекомендуем вам также экспортировать недекорированный компонент:

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'

Вы должны использовать указанный экспорт только для тестов.

JS-разработчик
источник
1
Этот ответ тоже верен. Отредактировал вашу ссылку, чтобы она соответствовала привязке.
Erowlin
Этот ответ имеет смысл! Это может быть не во всех случаях, но определенно лучше, чем играть с Провайдером и все такое, когда в этом нет необходимости.
lokori
Спасибо @lokori Счастливо, что тебе понравилось!
JS dev
2
Это был самый быстрый и простой способ снова сдать тест.
Майк Лайонс
2
«Вы бы использовали указанный экспорт только для тестов». -- Работает для меня.
technazi
7

Когда мы собираем приложение response-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 } });

jose920405
источник
3

Для меня это была проблема с импортом, надеюсь, это поможет. Импорт по умолчанию WebStorm был неправильным.

заменить

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

с участием

import {connect} from "react-redux";
ATQSHL
источник
2

jus сделайте это import {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

Это случилось со мной, когда я обновился. Мне пришлось вернуться на более раннюю версию.

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

коди
источник
Это больше комментарий, чем ответ
sudo97
Было много критических изменений. Я рекомендую посмотреть это видео, чтобы лучше понять изменения youtube.com/watch?v=yOZ4Ml9LlWE
Камил Дзенишевский
1

в конце вашего 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'));

Mehrdad
источник