고정 위반 : "Connect (SportsDatabase)"의 컨텍스트 또는 소품에서 "store"를 찾을 수 없습니다.


142

전체 코드는 여기 : 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)

답변:


182

꽤 간단합니다. 을 호출하여 생성 된 래퍼 구성 요소를 테스트하려고합니다 connect()(MyPlainComponent). 해당 랩퍼 구성 요소는 Redux 저장소에 액세스 할 것으로 예상합니다. 일반적으로 해당 상점은로 사용할 수 context.store있습니다. 구성 요소 계층의 맨 위에는가 있기 때문 <Provider store={myStore} />입니다. 그러나 연결된 구성 요소를 자체 저장소로 렌더링하지 않으므로 오류가 발생합니다.

몇 가지 옵션이 있습니다.

  • 상점을 작성하고 <Provider>연결된 컴포넌트 주변을 렌더링 하십시오.
  • <MyConnectedComponent store={store} />연결된 구성 요소도 소품으로 "store"를 허용 하므로 상점을 작성하고로 직접 전달하십시오.
  • 연결된 구성 요소를 테스트하지 마십시오. "일반", 연결되지 않은 버전을 내보내고 대신 테스트하십시오. 일반 구성 요소와 mapStateToProps기능 을 테스트 하면 연결된 버전이 올바르게 작동한다고 안전하게 가정 할 수 있습니다.

Redux 문서의 "Testing"페이지 ( https://redux.js.org/recipes/writing-tests) 를 읽고 싶을 것입니다 .

편집 :

실제로 소스를 게시하고 오류 메시지를 다시 읽은 후에 실제 문제는 SportsTopPane 구성 요소에 있지 않습니다. 문제는 당신이 SportsTopPane을 "완전히"렌더링하려고하는데, 이것은 첫 번째 경우와 같이 "얕은"렌더링을하지 않고 모든 자식을 렌더링한다는 것입니다. 이 라인 searchComponent = <SportsDatabase sportsWholeFramework="desktop" />;은 내가 생각하는 구성 요소도 연결되어 있으므로 React의 "컨텍스트"기능에서 상점을 사용할 수있을 것으로 기대합니다.

이 시점에서 두 가지 새로운 옵션이 있습니다.

  • SportsTopPane의 "얕은"렌더링 만 수행하여 자식을 완전히 렌더링하도록 강요하지 않습니다.
  • SportsTopPane을 "깊게"렌더링하려면 컨텍스트에서 Redux 저장소를 제공해야합니다. Enzyme 테스트 라이브러리를 살펴 보는 것이 좋습니다. 예제는 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 2016 년

97

jest와 함께 일한 가능한 솔루션

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 래퍼 = shallow (<Provider store = {store}> <App /> </ Provider>); expect (wrapper.contains (<App />)).toBe(true);
작은 뇌

69

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)

기본 내보내기는 여전히 꾸며진 구성 요소이므로 위 그림과 같은 import 문은 이전과 같이 작동하므로 응용 프로그램 코드를 변경할 필요가 없습니다. 그러나 이제 다음과 같이 장식되지 않은 앱 구성 요소를 테스트 파일로 가져올 수 있습니다.

// 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
이 답변도 합법적입니다. 앵커와 일치하도록 링크를 편집했습니다.
Erowlin

이 답변은 완벽합니다. 모든 경우에 옳은 것은 아니지만 공급자와 함께하는 것보다 필요하지 않은 경우보다 낫습니다.
lokori

감사합니다 @lokori 당신이 좋아합니다!
Vishal Gulati

2
이것은 내 시험을 다시 통과시키는 가장 빠르고 간단한 방법이었습니다.
Mike Lyons 21

2
"명명 된 내보내기 만 테스트에 사용합니다." -저에게 효과적입니다.
technazi

7

react-redux 애플리케이션을 만들 때 맨 위에 Providerredux 저장소의 인스턴스 가있는 태그 가있는 구조를 볼 수 있어야합니다 .

그런 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는 "enzyme"에서 가져 오기 {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";


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

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.