Vi phạm bất biến: Không thể tìm thấy Cửa hàng lưu trữ trong bối cảnh hoặc đạo cụ của Kết nối (Thể thao cơ sở dữ liệu)


142

Mã đầy đủ ở đây: https://gist.github.com/js08/0ec3d70dfda76d7e9fb4

Chào,

  • Tôi có một ứng dụng hiển thị các mẫu khác nhau cho máy tính để bàn và thiết bị di động trên cơ sở môi trường xây dựng.
  • Tôi thành công có thể phát triển nó khi tôi cần ẩn menu điều hướng cho mẫu di động của mình.
  • ngay bây giờ tôi có thể viết một trường hợp thử nghiệm trong đó nó tìm nạp tất cả các giá trị thông qua các proptype và kết xuất chính xác
  • nhưng không chắc chắn làm thế nào để viết các trường hợp thử nghiệm đơn vị khi thiết bị di động của nó không nên kết xuất thành phần điều hướng.
  • Tôi đã thử nhưng tôi gặp phải một lỗi ... bạn có thể cho tôi biết cách khắc phục không.
  • chứng minh mã dưới đây.

Trường hợp thử nghiệm

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

});

Đoạn mã nơi trường hợp kiểm tra cần phải được viết

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

lỗi

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)

Câu trả lời:


182

Nó khá đơn giản. Bạn đang cố kiểm tra thành phần trình bao bọc được tạo bằng cách gọi connect()(MyPlainComponent). Thành phần trình bao bọc đó dự kiến ​​sẽ có quyền truy cập vào cửa hàng Redux. Thông thường cửa hàng đó có sẵn như là context.store, bởi vì ở đầu phân cấp thành phần của bạn, bạn có một <Provider store={myStore} />. Tuy nhiên, bạn đang tự hiển thị thành phần được kết nối của mình, không có cửa hàng, do đó, nó sẽ gây ra lỗi.

Bạn có một vài lựa chọn:

  • Tạo một cửa hàng và hiển thị <Provider>xung quanh thành phần được kết nối của bạn
  • Tạo một cửa hàng và trực tiếp chuyển nó vào <MyConnectedComponent store={store} />, vì thành phần được kết nối cũng sẽ chấp nhận "lưu trữ" làm chỗ dựa
  • Đừng bận tâm kiểm tra thành phần được kết nối. Xuất "đơn giản", phiên bản không kết nối và thay vào đó kiểm tra. Nếu bạn kiểm tra thành phần đơn giản và mapStateToPropschức năng của mình , bạn có thể cho rằng phiên bản được kết nối sẽ hoạt động chính xác.

Bạn có thể muốn đọc qua trang "Kiểm tra" trong tài liệu Redux: https://redux.js.org/recipes/wr-tests .

chỉnh sửa :

Sau khi thực sự thấy rằng bạn đã đăng nguồn và đọc lại thông báo lỗi, vấn đề thực sự không nằm ở thành phần SportsTopPane. Vấn đề là bạn đang cố gắng "hoàn toàn" kết xuất SportsTopPane, cũng kết xuất tất cả các con của nó, thay vì thực hiện kết xuất "nông" như bạn trong trường hợp đầu tiên. Dòng searchComponent = <SportsDatabase sportsWholeFramework="desktop" />;này hiển thị một thành phần mà tôi giả sử cũng được kết nối và do đó hy vọng một cửa hàng sẽ có sẵn trong tính năng "bối cảnh" của React.

Tại thời điểm này, bạn có hai tùy chọn mới:

  • Chỉ thực hiện kết xuất "nông" của SportsTopPane, để bạn không buộc nó hiển thị hoàn toàn con của nó
  • Nếu bạn muốn thực hiện kết xuất "sâu" SportsTopPane, bạn sẽ cần cung cấp một cửa hàng Redux theo ngữ cảnh. Tôi thực sự khuyên bạn nên xem thư viện thử nghiệm Enzyme, cho phép bạn làm chính xác điều đó. Xem http://airbnb.io/enzyme/docs/api/ReactWrapper/setContext.html để biết ví dụ.

Nhìn chung, tôi sẽ lưu ý rằng bạn có thể đang cố gắng làm quá nhiều trong một thành phần này và có thể muốn xem xét việc chia nó thành các phần nhỏ hơn với ít logic hơn cho mỗi thành phần.


Tôi đã thử nhưng không biết phải làm thế nào ... bạn có thể cập nhật trong các trường hợp thử nghiệm của tôi không

1
Tôi giả sử rằng trong SportsTopPortion.js, bạn có let SportsTopPortion = connect(mapStateToProps)(SomeOtherComponent). Câu trả lời đơn giản nhất là kiểm tra thành phần khác đó , không phải thành phần được trả về connect.
Markerikson

1
Aha. Bây giờ tôi thấy những gì đang xảy ra. Vấn đề không nằm ở chính SportsTopPane. Vấn đề là bạn đang thực hiện kết xuất "đầy đủ" của SportsTopPane chứ không phải kết xuất "nông", vì vậy React đang cố gắng kết xuất hoàn toàn tất cả trẻ em. Thông báo lỗi đề cập đến dòng searchComponent = <SportsDatabase sportsWholeFramework="desktop" />;. Đó là thành phần được kết nối đang mong đợi một cửa hàng và phá vỡ. Vì vậy, hai đề xuất mới: hoặc chỉ hiển thị nông cạn SportsTopPane hoặc sử dụng thư viện như Enzyme để kiểm tra. Xem airbnb.io/enzyme/docs/api/ReactWrapper/setContext.html .
Markerikson

bạn có thể cho tôi biết cách viết trường hợp thử nghiệm cho kịch bản này không `` `

3
Trả lời một người bị mắc kẹt hoặc bối rối với cụm từ "nó khá đơn giản" có thể trở nên chê bai hoặc gay gắt. Vui lòng sử dụng một cách tiết kiệm.
jayqui

97

Giải pháp có thể có hiệu quả với tôi

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
hoạt động tốt, thay vì phải truyền đạo cụ từng cái một.
ghostkraviz

2
Cảm ơn bạn, một giải pháp rất tốt đẹp. Tôi gặp vấn đề này vì tôi đang sử dụng một thành phần Ứng dụng cấp cao nhất với định tuyến và cửa hàng được cung cấp cho ứng dụng con trong mỗi tuyến đường nên tôi không phải chuyển đạo cụ vào bộ định tuyến. Tôi đã thay đổi nó một chút để sử dụng. const Wrapper = nông (<Nhà cung cấp cửa hàng = {cửa hàng}> <Ứng dụng /> </ Nhà cung cấp>); mong đợi (Wrapper.contains (<Ứng dụng />)).toBe(true);
Não nhỏ

69

Như các tài liệu chính thức của redux đề xuất, tốt hơn là xuất thành phần không được kết nối là tốt.

Để có thể tự kiểm tra thành phần Ứng dụng mà không phải đối phó với trình trang trí, chúng tôi khuyên bạn cũng nên xuất thành phần chưa được trang trí:

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)

Vì xuất mặc định vẫn là thành phần được trang trí, nên câu lệnh nhập trong hình sẽ hoạt động như trước vì vậy bạn sẽ không phải thay đổi mã ứng dụng của mình. Tuy nhiên, bây giờ bạn có thể nhập các thành phần Ứng dụng chưa được trang trí trong tệp thử nghiệm của mình như sau:

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

Và nếu bạn cần cả hai:

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

Trong chính ứng dụng, bạn vẫn sẽ nhập nó bình thường:

import App from './App'

Bạn sẽ chỉ sử dụng xuất khẩu được đặt tên cho các bài kiểm tra.


1
Câu trả lời này là hợp pháp là tốt. Chỉnh sửa liên kết của bạn để phù hợp với neo.
Erowlin

Câu trả lời này có ý nghĩa hoàn hảo! Nó có thể không phải là điều đúng đắn trong mọi trường hợp, nhưng chắc chắn tốt hơn là chơi với Nhà cung cấp và tất cả những điều đó khi không cần thiết.
lokori

Cảm ơn @lokori Chúc bạn thích nó!
Vishal Gulati

2
Đây là cách nhanh nhất và đơn giản nhất để vượt qua bài kiểm tra của tôi.
Mike Lyons

2
"Bạn sẽ chỉ sử dụng xuất khẩu được đặt tên cho các thử nghiệm." - Làm việc cho tôi.
Technazi

7

Khi chúng ta kết hợp một ứng dụng redux phản ứng, chúng ta sẽ thấy một cấu trúc trong đó ở trên cùng chúng ta có Providerthẻ có một thể hiện của một cửa hàng redux.

Đó Providerthẻ sau đó làm cho thành phần cha mẹ của bạn, cho phép gọi nó là Appthành phần mà lần lượt làm cho mọi thành phần khác bên trong ứng dụng.

Đây là phần quan trọng, khi chúng ta bọc một thành phần với connect()hàm, connect()hàm đó dự kiến ​​sẽ thấy một số thành phần cha trong cấu trúc phân cấp có Providerthẻ.

Vì vậy, ví dụ bạn đặt connect()hàm vào đó, nó sẽ tra cứu thứ bậc và cố gắng tìm Provider.

Đó là những gì bạn muốn xảy ra, nhưng trong môi trường thử nghiệm của bạn, dòng chảy đó đang bị phá vỡ.

Tại sao?

Tại sao?

Khi chúng tôi quay lại tệp thử nghiệm sportsDatabase giả định, bạn phải tự mình là thành phần sportsDatabase và sau đó cố gắng tự hiển thị thành phần đó.

Vì vậy, về cơ bản những gì bạn đang làm bên trong tệp kiểm tra đó chỉ là lấy thành phần đó và bỏ qua nó một cách tự nhiên và nó không có mối quan hệ nào với bất kỳ Providerhoặc lưu trữ nào phía trên nó và đó là lý do tại sao bạn nhìn thấy thông báo này.

Không có lưu trữ hoặc Providerthẻ trong ngữ cảnh hoặc chỗ dựa của thành phần đó và vì vậy thành phần đó gây ra lỗi vì nó muốn xem Providerthẻ hoặc lưu trữ trong hệ thống phân cấp cha của nó.

Vì vậy, đó là những gì có nghĩa là lỗi.


6

trong trường hợp của tôi chỉ

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

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

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


2

jus thực hiện việc nhập {nông, gắn kết} này từ "enzyme";

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

Đối với tôi đó là vấn đề nhập khẩu, hy vọng nó sẽ giúp. nhập mặc định bởi WebStorm đã sai.

thay thế

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

với

import {connect} from "react-redux";

1

Điều này xảy ra với tôi khi tôi nâng cấp. Tôi đã phải hạ cấp trở lại.

phản ứng-redux ^ 5.0.6 → ^ 7.1.3


Đây là một nhận xét nhiều hơn là một asnwer
sudo97

Có rất nhiều thay đổi đột phá. Tôi khuyên bạn nên xem video này để hiểu rõ hơn về những thay đổi youtube.com/watch?v=yOZ4Ml9Llwe
Kamil Dzieniszewski

0

vào cuối Index.js của bạn cần thêm Mã này:

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

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.