Değişmez İhlal: “Bağlan” (“Connect (SportsDatabase)” bağlamında ya da desteklerinde "mağaza" bulunamadı


142

Tam kodu burada: https://gist.github.com/js08/0ec3d70dfda76d7e9fb4

Selam,

  • Oluşturma ortamı temelinde masaüstü ve mobil cihazlar için farklı şablonlar gösteren bir uygulamam var.
  • Mobil şablonum için gezinme menüsünü gizlemem gereken yerde başarıyla geliştirebiliyorum.
  • şu anda tüm değerleri prototiplerden aldığı ve doğru bir şekilde oluşturduğu bir test senaryosu yazabiliyorum
  • ancak mobil olduğunda nav bileşenini oluşturmaması gereken birim test senaryolarının nasıl yazıldığından emin değilim.
  • Denedim ama bir hatayla karşılaşıyorum ... nasıl düzeltebileceğinizi söyleyebilir misiniz?
  • kanıt kodu aşağıda.

Test durumu

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

});

Test senaryosunun yazılması gereken kod snippet'i

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

Hata

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)

Yanıtlar:


182

Oldukça basit. Arayarak oluşturulan sarmalayıcı bileşenini test etmeye çalışıyorsunuz connect()(MyPlainComponent). Bu sarmalayıcı bileşeni bir Redux mağazasına erişmeyi bekliyor. Normalde bu mağaza kullanılabilir context.store, çünkü bileşen hiyerarşinizin en üstünde bir <Provider store={myStore} />. Bununla birlikte, bağlı bileşeninizi herhangi bir mağaza olmadan kendi başınıza oluşturuyorsunuz, bu yüzden bir hata atıyor.

Birkaç seçeneğiniz var:

  • Mağaza oluşturma <Provider>ve bağlı bileşeninizin çevresini oluşturma
  • Bir mağaza oluşturun ve <MyConnectedComponent store={store} />bağlı bileşen de "mağaza" özelliğini pervane olarak kabul edeceğinden, doğrudan mağazaya aktarın
  • Bağlı bileşeni test etmekten çekinmeyin. "Düz", bağlı olmayan sürümü dışa aktarın ve bunun yerine test edin. Düz bileşeninizi ve mapStateToPropsişlevinizi test ederseniz , bağlı sürümün düzgün çalışacağını güvenle varsayabilirsiniz.

Muhtemelen Redux belgelerindeki "Test" sayfasını okumak istersiniz: https://redux.js.org/recipes/writing-tests .

düzenleme :

Aslında kaynak gönderdiğini gördükten ve hata mesajını yeniden okuduktan sonra, asıl sorun SportsTopPane bileşeninde değil. Sorun, ilk durumda olduğu gibi "sığ" bir render yapmak yerine, tüm çocuklarını da yapan SportsTopPane'ı "tamamen" oluşturmaya çalışmanızdır. Çizgi searchComponent = <SportsDatabase sportsWholeFramework="desktop" />;, bağlı olduğunu düşündüğüm bir bileşeni de oluşturuyor ve bu nedenle bir mağazanın React'ın "bağlam" özelliğinde kullanılabilir olmasını bekliyor.

Bu noktada iki yeni seçeneğiniz var:

  • SportsTopPane'i yalnızca "sığ" hale getirin, böylece çocuklarını tamamen oluşturmaya zorlamıyorsunuz
  • SportsTopPane'in "derin" işlenmesini yapmak istiyorsanız, bağlamda bir Redux mağazası sağlamanız gerekir. Kesinlikle bunu yapmanızı sağlayan Enzim test kütüphanesine bir göz atmanızı öneririm. Örnek için http://airbnb.io/enzyme/docs/api/ReactWrapper/setContext.html adresine bakın .

Genel olarak, bu bileşende çok fazla şey yapmaya çalıştığınızı ve bileşen başına daha az mantıkla daha küçük parçalara bölmeyi düşünmek isteyebileceğinizi unutmayın.


Denedim ama nasıl yapılacağından emin değilim ... test durumlarımda güncelleme yapabilir misiniz

1
SportsTopPortion.js'de var olduğunu varsayıyorum let SportsTopPortion = connect(mapStateToProps)(SomeOtherComponent). En kolay cevap, döndürülen bileşeni değil, diğer bileşeni test etmektir connect.
markerikson

1
Aha. Şimdi ne olduğunu görüyorum. Sorun SportsTopPane'ın kendisinde değil. Sorun şu ki, "sığ" bir render değil, SportsTopPane "tam" bir render yapmak, bu nedenle React tüm çocukları tamamen render çalışıyor. Hata mesajı satırı ifade eder searchComponent = <SportsDatabase sportsWholeFramework="desktop" />;. Yani bir mağaza ve kırılmasını bekliyor bağlı bir bileşendir. Yani, iki yeni öneri: ya sadece SportsTopPane'nin sığ görüntüsünü yapın ya da test etmek için Enzim gibi bir kütüphane kullanın. Bkz. Airbnb.io/enzyme/docs/api/ReactWrapper/setContext.html .
markerikson

Bana bu senaryo için test senaryosunun nasıl yazılacağını söyleyebilir misin `` ama mobil cihazının nav bileşenini oluşturmaması gerektiğinde birim test vakalarının nasıl yazılacağından emin değil misiniz. ``

3
"Oldukça basit" ifadesi ile sıkışmış veya şaşkın birine cevap vermek, aşağılayıcı veya sert olarak ortaya çıkabilir. Lütfen az miktarda kullanın.
jayqui

97

Jest ile benim için çalışan olası çözüm

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
sahne teker teker geçmek yerine iyi çalışıyor.
ghostkraviz

2
Teşekkür ederim, çok güzel bir çözüm. Yönlendirme ile üst düzey bir Uygulama bileşeni kullanıyorum ve mağaza yönlendirici içine geçmek zorunda değilsiniz bu yüzden her rotada mağaza alt uygulaması sağlanan bu sorun vardı. Kullanımım için biraz değiştirdim. const wrapper = sığ (<Sağlayıcı mağazası = {mağaza}> <Uygulama /> </Provider>); bekliyoruz (wrapper.contains (<App />)).toBe(true);
Little Brain

69

Redux'nun resmi belgelerinin önerdiği gibi, bağlı olmayan bileşeni de ihraç etmek daha iyidir.

Dekoratörle uğraşmak zorunda kalmadan Uygulama bileşeninin kendisini test edebilmek için, ayrıca dekore edilmemiş bileşeni de dışa aktarmanızı öneririz:

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)

Varsayılan dışa aktarma yine de dekore edilmiş bileşen olduğundan, yukarıda gösterilen içe aktarma ifadesi daha önce olduğu gibi çalışır, böylece uygulama kodunuzu değiştirmeniz gerekmez. Ancak, şimdi test dosyanızdaki dekore edilmemiş Uygulama bileşenlerini şu şekilde içe aktarabilirsiniz:

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

Her ikisine de ihtiyacınız varsa:

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

Uygulamanın kendisinde, normal olarak içe aktarırsınız:

import App from './App'

Testler için yalnızca adlandırılmış dışa aktarmayı kullanırsınız.


1
Bu cevap da yasal. Bağlantınızı çapa ile eşleşecek şekilde düzenledi.
Erowlin

Bu cevap çok mantıklı! Her durumda doğru şey olmayabilir, ancak Sağlayıcı ve gerekli olmadığında tüm bunlarla kesinlikle daha iyi olabilir.
lokori

Teşekkürler @lokori Hoşuna gitti!
Vishal Gulati

2
Testimin tekrar geçmesini sağlamanın en hızlı ve basit yolu buydu.
Mike Lyons

2
"Testler için yalnızca adlandırılmış dışa aktarmayı kullanırsınız." -- Benim için çalışıyor.
technazi

7

Bir tepki-redux uygulamasını bir araya getirdiğimizde, tepede Providerbir redux mağazası örneği olan etikete sahip olduğumuz bir yapı görmeyi beklemeliyiz .

Bu Provideretiket daha sonra ana bileşeninizi oluşturur, buna Appuygulama içindeki diğer tüm bileşenleri oluşturan bileşen diyelim.

Temel kısım, connect()işlevin bir bileşenini sardığımızda , bu connect()işlev, Provideretiketi olan hiyerarşide bazı üst bileşenleri görmeyi bekler .

connect()İşlevi oraya koyduğunuzda , hiyerarşiye bakacak veProvider .

Bu olmasını istediğiniz şeydir, ancak test ortamınızda bu akış bozulur.

Neden?

Neden?

Varsayılan sportsDatabase test dosyasına geri döndüğümüzde, sportsDatabase bileşeni tek başına olmalı ve daha sonra bu bileşeni tek başına oluşturmaya çalışmalısınız.

Yani aslında bu test dosyası içinde ne yapıyorsun sadece o bileşeni alıp vahşi doğada atmak ve üzerinde herhangi bir Providerveya mağaza ile hiçbir bağları vardır ve bu yüzden bu mesajı görüyorsun.

ProviderBu bileşenin bağlamında veya pervanesinde depolama veya etiket yoktur ve bu nedenle bileşen Provider, üst hiyerarşisinde bir etiket veya mağaza görmek istediği için bir hata atar .

Bu hatanın anlamı budur.


6

benim durumumda sadece

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

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

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


2

jus bu "enzim" den ithal {sığ, montaj};

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

Benim için ithalat meselesiydi, umarım yardımcı olur. WebStorm tarafından varsayılan içe aktarma yanlıştı.

değiştirmek

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

ile

import {connect} from "react-redux";


0

Index.js'nizin sonuna bu Kodu eklemeniz gerekir:

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

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.