Redux: Prueba de componentes conectados que tienen componentes conectados anidados dentro

Creado en 10 nov. 2016  ·  21Comentarios  ·  Fuente: reduxjs/redux

Hola, esto es más una pregunta que un problema.

Recientemente comencé a usar React y Redux para un proyecto y tengo un par de problemas con las pruebas, pero la documentación no parece dar una solución concluyente al problema.

En la actualidad, tengo un componente conectado para el que me gustaría escribir algunas pruebas unitarias, sin embargo, hay un componente conectado anidado dentro del primer componente y parece que termino bloqueado por los accesorios o el estado (o en la mayoría de los casos ambos) de el componente anidado no se está configurando.

Me preguntaba si hay alguna forma de acoplar un componente para poder concentrarme solo en el componente que estoy tratando de probar.

Gracias por adelantado

question

Comentario más útil

La preocupación que se plantea aquí es cuando el componente que se está probando luego presenta otros componentes conectados, ya sea como hijos directos o en algún lugar de la jerarquía. Si realiza un renderizado completo a través de mount() , los componentes conectados descendientes intentarán absolutamente acceder a this.context.store y no encontrarlo. Por lo tanto, las opciones principales son asegurarse de que los descendientes no se procesen haciendo una prueba superficial o hacer que la tienda esté disponible en contexto.

Todos 21 comentarios

Dos cosas:

1) este es el uso y probablemente debería ir al desbordamiento de la pila.

2) parte de la conveniencia de usar el componente de orden superior connect
es que puede escribir una prueba unitaria para el componente unconnected y
confíe en redux para manejar la tubería de la tienda como debería

Si no está claro lo que quiero decir con eso, puedo publicar un ejemplo.

Un par de pensamientos:

  • Una forma de centrarse en un solo componente es utilizar la representación "superficial", que en realidad no representa ningún elemento secundario
  • También puede renderizar <Provider store={testStore}><ConnectedComponent /></Provider> en sus pruebas

Para su información, tengo enlaces a varios artículos sobre las pruebas de React / Redux aquí, que pueden resultarle útiles: https://github.com/markerikson/react-redux-links/blob/master/react-redux-testing. md .

Y sí, como pregunta de uso, es mejor formularla en otro lugar.

Gracias @markerikson Actualmente estoy probando el último método que mencionaste, pero también marcaré tu lista de enlaces :)

Sigo pensando que si quieres probar ese componente una vez de forma aislada, es mejor que hagas algo como

// Component.js
import React from 'react'

export const Component = ({ a, b, c }) => ( 
  // ...
)

// ...

export default connect(mapStateToProps, mapDispatchToProps)(Component)

entonces puede traer el componente antes de que esté conectado en su prueba como

// Component.spec.js
import { Component } from './Component.js'
import { mount } from 'enzyme'

it('should mount without exploding', () => {
  mount(<Component a={1} b={2} c={3} />)
})

Si lo que realmente desea probar es que los cambios en su tienda se están propagando correctamente, entonces tiene sentido representar el Provider y eso es diferente, aunque no estoy seguro de que obtenga mucho valor al probar eso.

La preocupación que se plantea aquí es cuando el componente que se está probando luego presenta otros componentes conectados, ya sea como hijos directos o en algún lugar de la jerarquía. Si realiza un renderizado completo a través de mount() , los componentes conectados descendientes intentarán absolutamente acceder a this.context.store y no encontrarlo. Por lo tanto, las opciones principales son asegurarse de que los descendientes no se procesen haciendo una prueba superficial o hacer que la tienda esté disponible en contexto.

@markerikson Ah, está bien, acabo de volver a leer. Pensé que el OP estaba tratando de probar el componente interno en lugar del externo. 👍

Gracias por su ayuda hasta ahora chicos,

El problema principal que tengo en este momento es que parece que no puedo pasar el estado al componente conectado que está anidado dentro, por lo que la vista no se representa correctamente.

El estado de burla en general es algo con lo que he estado luchando bastante y no he podido encontrar nada concluyente sobre el tema, por lo que sería genial si alguien pudiera darme una idea de cómo pasaría un estado de burla a un estado de burla. componente anidado conectado o de orden superior?

TIA

@StlthyLee : ¿a qué te refieres con "pasar el estado"? Si renderiza <Provider store={store}><ComponentWithConnectedDescendants /></Provider> en su prueba, eso debería manejar las cosas.

Ese es el problema, solo hacer eso no es manejarlo correctamente, el componente anidado dentro requiere un parámetro específico del estado de la aplicación que en la actualidad no está recibiendo por una razón u otra.

@StlthyLee probablemente debería poner un example / gist / codepen / repo-link o algo así, tomará menos tiempo detectar el problema ya que parece haber conflictos terminológicos aquí.

@Koleok espero que esto ayude a aclarar

https://gist.github.com/StlthyLee/02e116d945867a77c382fa0105af1bf3

Si no, pregunte, ya que esto es algo a lo que estoy ansioso por llegar al fondo, después de haber pasado los últimos 9-10 años trabajando en el mundo de Rails y tener una buena comprensión de TDD desde esa perspectiva, ahora estoy tratando de obtener mi cabeza alrededor de TDD en el mundo React / Redux.

Así que creo que he llegado al fondo de este problema ahora, no era tanto un problema de prueba sino más una copia y un error pasado cometido por otro miembro del equipo, supongo que esto es parte integral de tener que escribir el pruebas para ajustarse al código en lugar de mis métodos TDD preferidos. Gracias por toda la información proporcionada aquí ha sido sumamente valiosa

Solo para ofrecer otra opción que podría funcionar para algunos casos de uso, recientemente encontré este problema con un componente conectado anidado. En lugar de hacer una tienda simulada, exporté las versiones no conectadas de cada componente para realizar pruebas unitarias sin una tienda. Algo como esto:

class TopLevelImpl extends React.PureComponent {
  // Implementation goes here
}

// Export here for unit testing.  The unit test imports privates and uses TopLevel
// with Enzyme's mount().
export const privates = {
  TopLevel: TopLevelImpl,
}

export const TopLevel = connect(mapStateToProps)(TopLevelImpl)

Hice lo mismo con los componentes anidados secundarios para que cada uno se pueda probar de forma independiente.

Luego tomé el componente conectado de nivel superior y pasé los componentes conectados al niño como accesorios en la función render() . Sin embargo, cuando realizo una prueba unitaria del componente de nivel superior, en lugar de pasar los componentes conectados, simplemente paso los componentes simulados para asegurarme de que se representan en el lugar correcto. Sin embargo, también puede pasar los componentes no conectados.

Algo como esto:

// Root render function
render() {
  return (
    <Provider store={store}>
      <TopLevel child1={<Child1 />} child2={<Child2 />} />
    </Provider>
  )
}

De hecho, me gusta tu idea. Pero hago algo como esto en su lugar

const props = {
  child1: () => (<div />),
  child2: () => (<div />)
}
<TopLevel {...props} />

Creo que si ya probó child1 y child2, probablemente no sea necesario tenerlos en su componente TopLevel.

También paso una función si necesito renderizar condicionalmente el componente.

Sin embargo, no sigo tu última oración. ¿Qué tienen que ver las pruebas con pasar componentes secundarios a otro componente?

Bien. Por ejemplo, tengo un componente externo conectado que utiliza uno o pocos componentes internos conectados . En este caso, si quiero probar el componente externo, hago algo como esto

wrapper = mount(<ExternalComponent.WrappedComponent {...props} />)

Sin embargo, tengo un error de que mis componentes internos necesitan un estado para funcionar correctamente.

Invariant Violation: Could not find "store" in either the context or props

Luego intenté pasar el estado simulado, etc. Funcionó. Sin embargo, en mi caso, tengo que realizar una gran cantidad de inicialización para cada componente conectado, incluidas las llamadas asíncronas, initialState, etc., y tuve otros errores relacionados con eso.

Creo que será una exageración hacer toda la inicialización adecuada para todos los componentes internos conectados si solo quiero probar el componente externo.

Así que decidí sacar todos los componentes conectados y pasarlos como accesorios al componente externo conectado. Y ahora puedo reemplazarlos con un div vacío cuando quiero probar.

Oh, sí, esa fue solo la motivación original para el patrón. Entonces, para modificar su oración, quiso decir "Creo que si ya ha probado child1 y child2, entonces no hay necesidad de pasarlos a su componente TopLevel cuando lo pruebe unitariamente".

Convenido.

@StlthyLee , Desafortunadamente, enfrenté el mismo problema al intentar probar un componente que tiene componentes conectados anidados.

Una solución que encontré particularmente útil es conectar el componente anidado a la tienda redux según el entorno de prueba.

  • Cuando se ejecuten las pruebas, configure una variable de entorno NODE_ENV="test" . Puede capturar esta variable y hacer que el componente reciba propiedades predeterminadas. En tal caso, el componente no está conectado a la tienda y, al probar el componente principal, no aparecen problemas.
  • En otros casos, conecte el componente. Este es el escenario predeterminado cuando se ejecuta el componente en la aplicación.

a) Primero, necesitará una función auxiliar que determine si el entorno es test :

export default function ifTestEnv(forTestEnv, forNonTestEnv) {
  const isTestEnv = process && process.env && process.env.NODE_ENV === 'test';
  const hoc = isTestEnv ? forTestEnv : forNonTestEnv;
  return function(component) {
    return hoc(component);
  };
}

b) A continuación, defaultProps() y connect() deben aplicarse condicionalmente según el entorno:

import React, { Component } from 'react';
import { connect } from 'react-redux';
import defaultProps from 'recompose/defaultProps';

import ifTestEnv from 'helpers/if_test_env';
import { fetch } from './actions';

class MyComponent extends Component {
   render() {
      const { propA, propB } = this.props;
      return <div>{propA} {probB}</div>
   }

   componentDidMount() {
      this.props.fetch();
   }
}

function mapStateToProps(state) {
  return {
    propA: state.valueA,
    propB: state.valueB
  };
}

export default ifTestEnv(
  defaultProps({
    propA: 'defaultA',
    propB: 'defaultB',
    fetch() {   }
  }),
  connect(mapStateToProps, { fetch })
)(MyComponent);

c) Asegúrese de que NODE_ENV sea "test" cuando se ejecuten las pruebas:

  • Cuando utilice la CLI de mocha, ponga un prefijo para configurar el entorno de prueba: NODE_ENV='test' mocha app/**/*.test.jsx .
  • En el caso de un paquete web, aplique un complemento:
plugins: [
    // plugins...
    new webpack.DefinePlugin({
      'process.env': {
        NODE_ENV: JSON.stringify('test')
      },
    })
]

@markerikson Gracias por las dos sugerencias. Probé el segundo y envolví mi componente para probarlo con un proveedor. En este caso, mapStateToProps funciona correctamente pero no obtengo los resultados esperados con mapDispatchToProps . Aquí, el componente que se está probando representa otros componentes conectados.

Mi mapDispatchToProps se parece a

/* <strong i="10">@flow</strong> */
import { bindActionCreators } from 'redux';

import type { Dispatch } from './types';
import * as actions from './actions';

export default (dispatch: Dispatch, ownProps: Object): Object => ({
  actions: bindActionCreators(actions, dispatch),
});

aquí el valor de las acciones devueltas es undefined .

store.js

import { applyMiddleware, compose, createStore } from 'redux';
import { autoRehydrate } from 'redux-persist';
import thunk from 'redux-thunk';

import rootReducer from './reducers';
import { REHYDRATE } from 'redux-persist/constants';

const store = compose(autoRehydrate(), applyMiddleware(thunk, createActionBuffer(REHYDRATE)))(createStore)(rootReducer);

export default store;

Me doy cuenta de que este es un hilo bastante antiguo, pero me pareció un problema un poco molesto y pensé que esta solución podría ayudar a alguien que se topa con este hilo (como yo).

Como en realidad no estaba probando nada relacionado con Redux, una solución simple que encontré fue simular la función de conexión para devolver solo el componente inalterado que se le pasó.

Por ejemplo, en su aplicación puede tener un componente:


export class ExampleApp extends Component {
  render() {
  }
}

export default connect(
  null,
  { someAction }
)(ExampleApp);

luego, al comienzo de su archivo de prueba, puede colocar una función de conexión simulada para evitar que Redux interactúe con su componente:

jest.mock("react-redux", () => {
  return {
    connect: (mapStateToProps, mapDispatchToProps) => (
      ReactComponent
    ) => ReactComponent
  };
});

Esta burla deberá colocarse en cada archivo de prueba que monte a un niño conectado.

@scottbanyard ¿cómo se pasan los accesorios al componente secundario?

¿Fue útil esta página
0 / 5 - 0 calificaciones

Temas relacionados

timdorr picture timdorr  ·  3Comentarios

cloudfroster picture cloudfroster  ·  3Comentarios

elado picture elado  ·  3Comentarios

mickeyreiss-visor picture mickeyreiss-visor  ·  3Comentarios

vraa picture vraa  ·  3Comentarios