Infracción invariable: no se pudo encontrar la "tienda" en el contexto ni en los accesorios de "Connect (SportsDatabase)"

142

Código completo aquí: https://gist.github.com/js08/0ec3d70dfda76d7e9fb4

Hola,

  • Tengo una aplicación donde muestra diferentes plantillas para escritorio y dispositivos móviles en función del entorno de compilación.
  • Puedo desarrollarlo con éxito donde necesito ocultar el menú de navegación de mi plantilla móvil.
  • en este momento puedo escribir un caso de prueba en el que se obtienen todos los valores a través de los tipos y se representa correctamente
  • pero no estoy seguro de cómo escribir los casos de prueba de la unidad cuando es móvil, no debería representar el componente de navegación.
  • Lo intenté pero me enfrento a un error ... ¿puede decirme cómo solucionarlo?
  • Código de abajo.

Caso de prueba

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

});

Fragmento de código donde se debe escribir el caso de prueba

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

Error

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)

fuente

Respuestas:

182

Es muy simple Estás intentando probar el componente de envoltorio generado al llamar connect()(MyPlainComponent). Ese componente de contenedor espera tener acceso a una tienda Redux. Normalmente esa tienda está disponible como context.store, porque en la parte superior de su jerarquía de componentes tendría un <Provider store={myStore} />. Sin embargo, está renderizando su componente conectado por sí mismo, sin tienda, por lo que arroja un error.

Tienes algunas opciones:

  • Crea una tienda y renderiza un <Provider> alrededor de tu componente conectado
  • Cree una tienda y páselo directamente como <MyConnectedComponent store={store} />, ya que el componente conectado también aceptará "almacenar" como accesorio
  • No te molestes en probar el componente conectado. Exporte la versión "simple", sin conexión, y pruébelo en su lugar. Si prueba su componente simple y su mapStateToPropsfunción, puede asumir con seguridad que la versión conectada funcionará correctamente.

Probablemente desee leer la página "Pruebas" en los documentos de Redux: https://redux.js.org/recipes/writing-tests .

editar :

Después de ver realmente que publicó la fuente y volver a leer el mensaje de error, el verdadero problema no está en el componente SportsTopPane. El problema es que está intentando renderizar "completamente" SportsTopPane, que también representa a todos sus elementos secundarios, en lugar de hacer un renderizado "superficial" como lo fue en el primer caso. La línea searchComponent = <SportsDatabase sportsWholeFramework="desktop" />;está representando un componente que supongo que también está conectado, y por lo tanto espera que una tienda esté disponible en la función "contexto" de React.

En este punto, tiene dos nuevas opciones:

  • Solo haga renderizado "superficial" de SportsTopPane, de modo que no lo esté forzando a renderizar completamente a sus hijos
  • Si desea hacer una representación "profunda" de SportsTopPane, deberá proporcionar una tienda Redux en contexto. Le recomiendo que eche un vistazo a la biblioteca de pruebas de Enzyme, que le permite hacer exactamente eso. Consulte http://airbnb.io/enzyme/docs/api/ReactWrapper/setContext.html para ver un ejemplo.

En general, me gustaría señalar que podría estar intentando hacer demasiado en este componente y podría considerar dividirlo en partes más pequeñas con menos lógica por componente.

rotulador
fuente
Intenté pero no estoy seguro de cómo hacerlo ... ¿puedes actualizar en mis casos de prueba
1
Supongo que en SportsTopPortion.js, tienes let SportsTopPortion = connect(mapStateToProps)(SomeOtherComponent). La respuesta más fácil es probar ese otro componente, no el componente devuelto por connect.
markerikson
1
Ajá. Ahora veo lo que está pasando. El problema no es con SportsTopPane en sí. El problema es que está haciendo un renderizado "completo" de SportsTopPane, no un renderizado "superficial", por lo que React está tratando de renderizar completamente a todos los niños. El mensaje de error se refiere a la línea searchComponent = <SportsDatabase sportsWholeFramework="desktop" />;. Ese es el componente conectado que espera una tienda y se rompe. Entonces, dos nuevas sugerencias: ya sea solo renderizar superficialmente SportsTopPane, o usar una biblioteca como Enzyme para probar. Ver airbnb.io/enzyme/docs/api/ReactWrapper/setContext.html .
markerikson
¿Puede decirme cómo escribir el caso de prueba para este escenario? `` `
3
Contestar a alguien que está atrapado o confundido con la frase "es bastante simple" puede parecer despectivo o duro. Por favor, use con moderación.
jayqui
97

Posible solución que me funcionó con broma

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);
    });
});
codeislife
fuente
1
funciona bien, en lugar de tener que pasar accesorios uno por uno.
ghostkraviz
2
Gracias, una muy buena solución. Tuve este problema porque estoy usando un componente de aplicación de nivel superior con enrutamiento y la tienda se proporciona a la aplicación secundaria en cada ruta para que no tenga que pasar accesorios al enrutador. Lo cambié un poco para mi uso. const wrapper = shallow (<proveedor store = {store}> <App /> </Provider>); esperar (wrapper.contains (<App />)).toBe(true);
Little Brain
69

Como el oficial sugieren los documentos de redux, también es mejor exportar el componente no conectado.

Para poder probar el componente de la aplicación en sí sin tener que lidiar con el decorador, le recomendamos que también exporte el componente sin decorar:

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)

Dado que la exportación predeterminada sigue siendo el componente decorado, la declaración de importación que se muestra arriba funcionará como antes, por lo que no tendrá que cambiar el código de su aplicación. Sin embargo, ahora puede importar los componentes de la aplicación sin decorar en su archivo de prueba de esta manera:

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

Y si necesitas ambos:

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

En la aplicación en sí, aún la importaría normalmente:

import App from './App'

Solo usaría la exportación con nombre para las pruebas.

Vishal Gulati
fuente
1
Esta respuesta también es legítima. Editó su enlace para que coincida con el ancla.
Erowlin
¡Esta respuesta tiene mucho sentido! Puede que no sea lo correcto en todos los casos, pero definitivamente es mejor que jugar con el Proveedor y todo eso cuando no es necesario.
lokori
Gracias @lokori ¡Feliz que te guste!
Vishal Gulati
2
Esta fue la forma más rápida y sencilla de hacer que mi examen pase nuevamente.
Mike Lyons
2
"Solo usaría la exportación con nombre para las pruebas". -- Funciona para mi.
technazi
7

Cuando creamos una aplicación react-redux, deberíamos esperar ver una estructura donde en la parte superior tenemos la Provideretiqueta que tiene una instancia de una tienda redux.

Esa Provideretiqueta luego representa su componente principal, vamos a llamarlo el Appcomponente que a su vez representa todos los demás componentes dentro de la aplicación.

Aquí está la parte clave, cuando ajustamos un componente con la connect()función, esa connect()función espera ver algún componente padre dentro de la jerarquía que tiene la Provideretiqueta.

Entonces, la instancia en la que pones la connect()función allí, buscará la jerarquía e intentará encontrar elProvider .

Eso es lo que quiere que suceda, pero en su entorno de prueba ese flujo se está rompiendo.

¿Por qué?

¿Por qué?

Cuando volvemos al supuesto archivo de prueba sportsDatabase, debe ser el componente sportsDatabase por sí mismo y luego tratar de representar ese componente por sí solo de forma aislada.

Así que, esencialmente, lo que está haciendo dentro de ese archivo de prueba es solo tomar ese componente y simplemente arrojarlo en la naturaleza y no tiene vínculos con ninguno Provider o almacenar por encima y es por eso que está viendo este mensaje.

No hay ninguna tienda o Provideretiqueta en el contexto o accesorio de ese componente, por lo que el componente arroja un error porque quiere ver unProvider etiqueta o tienda en su jerarquía principal.

Entonces eso es lo que significa ese error.

Daniel
fuente
6

en mi caso solo

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

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

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

jose920405
fuente
2

simplemente haga esta importación {shallow, mount} de "enzima";

const store = mockStore({
  startup: { complete: false }
});

describe("==== Testing App ======", () => {
  const setUpFn = props => {
    return mount(
      <Provider store={store}>
        <App />
      </Provider>
    );
  };

  let wrapper;
  beforeEach(() => {
    wrapper = setUpFn();
  });
Hilal Aissani
fuente
2

Para mí fue un problema de importación, espero que ayude. la importación predeterminada por WebStorm fue incorrecta.

reemplazar

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

con

import {connect} from "react-redux";
ATQSHL
fuente
1

Esto me sucedió cuando actualicé. Tuve que rebajar de nuevo.

react-redux ^ 5.0.6 → ^ 7.1.3

codie
fuente
Esto es más un comentario que una respuesta
sudo97
Hubo muchos cambios importantes. Recomiendo ver este video para comprender mejor los cambios youtube.com/watch?v=yOZ4Ml9LlWE
Kamil Dzieniszewski
0

al final de su Index.js necesita agregar este Código:

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
fuente