Redux: No se puede hacer referencia a contenedores envueltos en un proveedor o por conexión con Enzyme

Creado en 20 mar. 2016  ·  51Comentarios  ·  Fuente: reduxjs/redux

Parece que no puedo hacer referencia a nada envuelto en un <Provider> y un connect

// test
let component = shallow(<Provider store={store}><ContainerComponent /></Provider>);
component.find('#abc'); // returns null

let component = shallow(<Provider store={store}><div id="abc"></div></Provider>);
component.find('#abc'); // returns the div node

// ContainerComponent
const Component = ({...}) => (<div id="abc"></div>);
export default connect(..., ...)(Component);

Seguí: https://github.com/reactjs/redux/issues/1481 a los ejemplos donde las pruebas se escribieron con enzima, sin embargo, los contenedores nunca se prueban en esos. Entonces, no sé si debo/puedo probar contenedores inteligentes.

@fshowalter ¿ Alguna idea?

Comentario más útil

Aunque no está directamente relacionado con Redux. Resolví esto llamando a shallow() en el contenedor nuevamente. Representa el componente interno con el estado de la tienda pasado.

Ejemplo:

import React from 'react';
import {expect} from 'chai';
import {shallow} from 'enzyme';
import configureMockStore from 'redux-mock-store';
import thunk from 'redux-thunk';
import events from 'events';

const mockStore = configureMockStore([ thunk ]);
const storeStateMock = {
  myReducer:{
    someState: 'ABC'
  }
};

let store;
let component;
describe('tests for MyContainerComponent', () => {
  beforeEach(() => {
    store = mockStore(storeStateMock);
    component = shallow(<MyContainerComponent store={store} />).shallow();
  });

  it('renders container', () => {
    expect(component.find('div.somediv')).to.have.length.of(1);
  });
  ...
});

Espero que esto ayude

Todos 51 comentarios

Ni connect() ni Provider son parte de esta biblioteca. Es más fácil discutir y rastrear estos problemas cuando se archivan en el repositorio apropiado: https://github.com/reactjs/react-redux.

Creo que en realidad tiene sentido. Dado que está renderizando superficialmente, solo se renderizará el componente Proveedor: su ContainerComponent simplemente se dejará en su forma de salida de objeto o lo que sea que produzca la renderización superficial.

Dos pensamientos aquí:

Primero, tenga en cuenta que el componente contenedor generado por connect() en realidad busca props.store antes de buscar context.store , por lo que si realmente siente que necesita probar un componente conectado , debería poder representar <ConnectedComponent store={myTestStore} /> sin necesidad de preocuparse por el proveedor o el contexto ni nada.

La segunda pregunta es si realmente necesita preocuparse por probar el componente completamente conectado. El argumento que he visto es que si puede probar su componente "simple" con accesorios específicos, y puede probar su implementación mapStateToProps , puede asumir con seguridad que react-redux los juntará correctamente, y no necesita probar la versión conectada en sí.

@gaearon tienes razón, lo siento. No sabía si plantear esto en el repositorio de reacción o enzima.

@markerikson La razón para probar el componente inteligente es para mapToDispatchProps donde quería asegurarme de que el componente envuelto llamara al despachador correcto. Simplemente pasar la tienda a ConnectedComponent significa que no probaré el mapeo de estado ni enviaré funciones de devolución de llamada.

Pensaré más en esto y plantearé un problema en el repositorio correspondiente si lo necesito. Gracias por la ayuda.

@mordra : cuando dice "se llamó al despachador correcto", ¿realmente quiere decir "creador de acción correcto"?

Podrías hacer algo como esto (probablemente la sintaxis esté mal, pero deberías hacerte una idea):

let actionSpy = sinon.spy();

let wrapper = shallow(<PlainComponent someActionProp={actionSpy} />);
wrapper.find(".triggerActionButton").simulate("click");
expect(actionSpy.calledOnce).to.be.true;
expect(actionSpy.calledWith({type : ACTION_I_WAS_EXPECTING)).to.be.true;

En otras palabras, renderice su componente sin formato, pase espías para los accesorios que habrían sido acciones devueltas desde mapDispatch y active cualquier comportamiento que el componente necesite para llamar a esas acciones.

Además, según mi comentario anterior: debería poder probar su mapStateToProps y mapDispatchToProps por separado, y sentirse seguro de que React-Redux los llamará apropiadamente, sin tener que intentar probar la versión conectada mismo para verificar que ese es el caso.

@markerikson No puedo hacer lo que sugieres porque mi PlainComponent no conoce acciones ni creadores de acciones. No sé si esta es la forma correcta de hacerlo, pero si nos fijamos en:
https://github.com/mordra/cotwmtor/blob/master/client/charCreation/charCreation.jsx
Verás que mi mapDispatchToProps contiene toda la lógica:

const mapDispatchToProps = (dispatch) => {
  return {
    onCompleted      : (player) => {
      Meteor.call('newGame', player, function (data) {
        console.log('new game return: ' + data);
      });
      dispatch(routeActions.push('/game'));
      dispatch({type: "INIT_GAME", map: generateAreas(), buildings: generateBuildings(dispatch)});
    },
    onCancelled      : () => dispatch(routeActions.push('/')),
    onChangeName     : input => dispatch(actions.changeName(input)),
    onChangeGender   : gender => dispatch(actions.setGender(gender)),
    onSetDifficulty  : lvl => dispatch(actions.setDifficulty(lvl)),
    onChangeAttribute: (attr, val) => dispatch(actions.setAttribute(attr, val))
  }
};

así que si espié los accesorios pasados ​​al PlainComponent, no podría probar si se llama a las acciones correctas, sino que solo los parámetros pasados ​​a los creadores de acciones son correctos.
Entonces necesitaría probar el componente connect por separado.

ah Eso me ayuda a entender más.

Entonces, con la advertencia de que en realidad tengo muy poca experiencia práctica escribiendo pruebas, un par de pensamientos:

  • Lo que realmente tiene allí son creadores de acciones, simplemente no están definidos por separado porque desea acceder a dispatch . Eso no es muy fácil de probar en sí mismo. Probablemente también querrá poder probar su comportamiento y, para hacerlo, querrá definirlos como sus propias funciones fuera de mapDispatch .
  • Todos ellos parecen muy buenos candidatos para usar con redux-thunk, lo que sin duda facilitaría la definición de estas funciones por sí mismos y les permitiría acceder a dispatch .
  • Su PlainComponent _sí_ conoce las "acciones", o al menos las devoluciones de llamada en este caso, en el sentido de que las está pasando, y en algún lugar de su componente está ejecutando this.props.onSetDifficulty("HARD") o algo así. Cuando sugerí pasar espías por acciones, este es el tipo de cosa que sugerí reemplazar. Entonces, si pasó un espía por onSetDifficulty , podría verificar que su componente lo llamó y pasó un valor aceptable para el nivel de dificultad.

En última instancia, creo que debería poder hacer que las cosas sean mucho más comprobables tomando esas funciones definidas en mapDispatch y definiéndolas por separado. Entonces no tendrá que preocuparse por tener mapDispatch conectado para probar su componente correctamente, y puede concentrarse en si el componente acaba de llamar o no a una devolución de llamada de apoyo dada con los argumentos correctos o algo así.

Además, desde el punto de vista del componente: en última instancia, no le preocupa si se envió {action: CHANGE_NAME, name : "Fred"} . Todo lo que sabe es que llamó a this.props.onChangeName("Fred") , y _eso_ es lo que debería intentar probar: que llamó a una devolución de llamada de apoyo de la manera correcta.

@mordra en su caso, exportaría mapDispatchToProps y lo probaría de forma aislada. Puede verificar que los nombres de las propiedades sean todos correctos y pasar un espía para que lo envíe para probar a sus creadores de acciones.

Dicho esto, tiendo a evitar mapDispatchToProps en favor de mapActionCreatorsToProps que mapea creadores de acciones ya formados que puedo probar fácilmente de forma aislada. En ese caso, solo pruebo que todos mis accesorios estén definidos para protegerse contra errores tipográficos de importación.

Finalmente, tenga en cuenta que puede usar el renderizado normal (no superficial). Necesitaría jsdom o un navegador real para eso, pero luego puede renderizar cualquier nivel de profundidad.

No sabía si aumentar esto en el repositorio de reacción o enzima.

Lo siento, no quise decir repositorios de React o Enzyme. Me refiero a React Redux , que es la biblioteca que estás usando.

Gracias por la ayuda chicos, es mucha información para mí para digerir. @markerikson @fshowalter Investigué un poco más y tomaré su sugerencia de exportar mapState/Dispatch y probarlos por separado, tiene sentido probar las devoluciones de llamada que evocan las acciones esperadas.

@gaearon El componente sin estado no se procesa sin poca profundidad y solo analiza los problemas, parece que hay problemas para procesar componentes con estado que tienen componentes sin estado anidados, por lo que tomé una nota mental para evitar ese camino por ahora.

No estoy seguro de a qué problemas te refieres. Puede usar renderizado superficial con componentes funcionales muy bien. Sin embargo, el renderizado superficial solo funciona en un nivel de profundidad (sin importar cómo se defina el componente). Esta es su característica principal: no recurre, por lo que las pruebas de componentes se mantienen independientes. Si tiene problemas específicos con el renderizado superficial que puede reproducir, presente los errores en el repositorio de React. ¡Gracias!

@gaearon : para aclarar, si hago esto:

let wrapper = shallow(<A><B /></A>);

¿Se renderizará B o no? Porque creo que esa era la pregunta original: intentar generar un <Provider> alrededor de un componente conectado para probar la conexión.

Aunque no está directamente relacionado con Redux. Resolví esto llamando a shallow() en el contenedor nuevamente. Representa el componente interno con el estado de la tienda pasado.

Ejemplo:

import React from 'react';
import {expect} from 'chai';
import {shallow} from 'enzyme';
import configureMockStore from 'redux-mock-store';
import thunk from 'redux-thunk';
import events from 'events';

const mockStore = configureMockStore([ thunk ]);
const storeStateMock = {
  myReducer:{
    someState: 'ABC'
  }
};

let store;
let component;
describe('tests for MyContainerComponent', () => {
  beforeEach(() => {
    store = mockStore(storeStateMock);
    component = shallow(<MyContainerComponent store={store} />).shallow();
  });

  it('renders container', () => {
    expect(component.find('div.somediv')).to.have.length.of(1);
  });
  ...
});

Espero que esto ayude

¿Deberíamos hacer una forma más accesible de hacer esto a través de un módulo personalizado, tal vez?

Editar: para que podamos declarar la conexión como un método para Enzyme, como component = shallowWithConnect()

@ev1stensberg Esta es una gran idea. ¿Hemos decidido si este es el enfoque y/o se ha comenzado a trabajar en esto? Si no, me encantaría contribuir.

Para cualquiera que se tope con este problema en el futuro, este es el enfoque que funciona para mí: simplemente pruebe el componente simple por sí mismo. Exporte tanto la definición del componente como una exportación con nombre como el componente conectado (para usar en su aplicación) como la exportación predeterminada. Volviendo al código de la pregunta original:

// test
let component = shallow(<Provider store={store}><ContainerComponent /></Provider>);
component.find('#abc'); // returns null

let component = shallow(<Provider store={store}><div id="abc"></div></Provider>);
component.find('#abc'); // returns the div node

// ContainerComponent
export const Component = ({...}) => (<div id="abc"></div>); // I EXPORT THIS, TOO, JUST FOR TESTING
export default connect(..., ...)(Component);

entonces solo hazlo

const component = shallow(<Component {...props} />)
// test component

Y como otros han señalado anteriormente, si también tiene cobertura para las funciones que pasa a connect, debe tener una cobertura completa para su componente en su conjunto.

Bueno, lo primero a tener en cuenta aquí es la filosofía de la :

  1. Comunicar el estado de la tienda a un
  2. Modificar el estado mediante acciones de despacho basadas en el eventos.

Si estoy bien entonces solo tienes que probar 2 cosas:

  1. ¿Su componente está recibiendo los accesorios correctos generados desde el estado (podría ser a través de selectores)?
  2. ¿Su componente está enviando las acciones correctas?

Así que este es mi enfoque

import React from 'react';
import { shallow } from 'enzyme';
import { fromJS } from 'immutable';
import { createStore } from 'redux';
// this is the <Map /> container
import Map from '../index';
// this is an action handled by a reducer
import { mSetRegion } from '../reducer';
// this is an action handled by a saga
import { sNewChan } from '../sagas';
// this is the component wrapped by the <Map /> container
import MapComponent from '../../../components/Map';

const region = {
  latitude: 20.1449858,
  longitude: -16.8884463,
  latitudeDelta: 0.0222,
  longitudeDelta: 0.0321,
};
const otherRegion = {
  latitude: 21.1449858,
  longitude: -12.8884463,
  latitudeDelta: 1.0222,
  longitudeDelta: 2.0321,
};
const coordinate = {
  latitude: 31.788,
  longitude: -102.43,
};
const marker1 = {
  coordinate,
};
const markers = [marker1];
const mapState = fromJS({
  region,
  markers,
});
const initialState = {
  map: mapState,
};
// we are not testing the reducer, so it's ok to have a do-nothing reducer
const reducer = state => state;
// just a mock
const dispatch = jest.fn();
// this is how i mock the dispatch method
const store = {
  ...createStore(reducer, initialState),
  dispatch,
};
const wrapper = shallow(
  <Map store={store} />,
);
const component = wrapper.find(MapComponent);

describe('<Map />', () => {
  it('should render', () => {
    expect(wrapper).toBeTruthy();
  });

  it('should pass the region as prop', () => {
    expect(component.props().region).toEqual(region);
  });

  it('should pass the markers as prop', () => {
    expect(component.props().markers).toEqual(markers);
  });
  // a signal is an action wich will be handled by a saga
  it('should dispatch an newChan signal', () => {
    component.simulate('longPress', { nativeEvent: { coordinate } });
    expect(dispatch.mock.calls.length).toBe(1);
    expect(dispatch.mock.calls[0][0]).toEqual(sNewChan(coordinate));
  });
  // a message is an action wich will be handled by a reducer
  it('should dispatch a setRegion message', () => {
    component.simulate('regionChange', otherRegion);
    expect(dispatch.mock.calls.length).toBe(2);
    expect(dispatch.mock.calls[1][0]).toEqual(mSetRegion(otherRegion));
  });
});

@markerikson , ¿qué quisiste decir con:

puede probar su implementación mapStateToProps

digamos que está exportando el contenedor conectado a través de una exportación ES6. No tendría acceso al mapStateToProps privado. ¿Estabas hablando de alguna otra manera o puedes ser específico?

@mordrax

Puede verificar que los nombres de las propiedades sean todos correctos

Entonces, si expone su mapStateToProps y lo hace público, digamos que renderiza su ContainerComponent de su prueba, incluido el envío de una tienda falsa y el envío de un accesorio de estado, entonces su mapStateToProps recibe ese estado, lo asigna a un accesorio. Pero entonces, ¿cómo puedes probarlo desde ese punto? Connect llamará a mapStateToProps fusionando así los accesorios, pero ¿dónde está la costura y qué punto es la costura en el código durante ese proceso/flujo donde puede interceptar los accesorios que se configuran en el componente de presentación? Supongo que la doble profundidad como @ guatedude2 podría ser una forma de ver los accesorios mapeados sin costuras...

también en tu superficial.superficial. Entonces, el componente connect () devuelve qué, un envoltorio, ¿verdad? ¿Y ese envoltorio qué envuelve el componente de presentación?

@granmoe , ¿puede explicar esto con más detalle en cuanto a lo que quiere decir exactamente?

si también tiene cobertura para las funciones que pasa a connect, debe tener cobertura completa para su componente en su conjunto.

También estoy con @tugorez en términos de _qué_ quiero probar y por qué.

@markerikson tan buen ejemplo con el espía. Es una cosa que puede probar, pero necesitaría más aserdores allí para probar la "unidad de comportamiento". Solo probar que se llamó a una acción de espionaje desde mapDispatchToProps realmente no nos dice toda la historia sobre el componente contenedor. No afirma el resultado esperado en función de la lógica del controlador de su contenedor.

No basta con probar que ha pasado las propiedades o el estado, quiere probar el comportamiento del componente contenedor. Eso significa probar dos cosas:

1) ¿El componente del contenedor conectó los accesorios al componente de presentación y, si lo hizo, hay un cierto estado que espero que tenga el componente de presentación una vez que se represente en función de ese estado específico pasado por los accesorios al componente de presentación? Quién sabe, tal vez aparezca algún desarrollador tonto y agregue más código a mapStateToProps, no siempre puede confiar en que mapeará las cosas correctamente, ese es el punto de probar la lógica del contenedor. Si bien no hay realmente ninguna lógica en mapStateToProps, quién sabe, nuevamente aparece un desarrollador y anuncia una declaración if allí ... bueno, ese es el comportamiento que podría estropear las cosas.

2) funciona la lógica del controlador de despacho (comportamiento) en el contenedor.

@dschinkel :

La práctica típica para definir componentes conectados a Redux es export default connect()(MyComponent) y también export MyComponent como una exportación con nombre. Del mismo modo, debido a que mapState es solo una función, también puede export const mapState = () => {} y luego importarlo y probarlo por separado.

En cuanto a probar el "componente contenedor" frente al "componente de presentación": la idea general aquí es que probablemente no necesite preocuparse por probar el contenedor en su mayor parte, donde "contenedor" === "la salida de connect ". React-Redux ya tiene un conjunto de pruebas completo sobre cómo debería comportarse, y no hay razón para que dupliques ese esfuerzo. _sabemos_ que maneja correctamente la suscripción a la tienda, llamando a mapState y mapDispatch , y pasando accesorios al componente envuelto.

Lo que _usted_ como consumidor de la biblioteca le interesará es cómo se comporta _su_ componente. En realidad, no debería importar si su propio componente obtiene accesorios de un envoltorio de connect , una prueba u otra cosa; es solo cómo se comporta ese componente dado un determinado conjunto de accesorios.

(Además, FWIW, me doy cuenta de que eres absolutamente un experto en pruebas y yo no, pero _sí_ parece que te estás volviendo un poco paranoico con las cosas :) Si te preocupa que un mapState sea accidentalmente roto, luego escriba pruebas para ello y siga adelante).

Ahora, si su componente envuelto luego renderiza otros componentes conectados dentro de él, y especialmente si está haciendo un renderizado completo en lugar de un renderizado superficial, entonces puede ser más sencillo probar las cosas haciendo const wrapper = mount(<Provider store={testStore}><MyConnectedComponent /></Provider> . Pero, en general, mi opinión es que la mayoría de las veces _debería_ poder probar su función mapState y el componente "simple" por separado, y realmente no debería intentar probar la versión conectada solo para verificar que la salida de mapState se pasa al componente simple.

Como referencia, es posible que desee ver la versión en miniatura de connect que Dan escribió hace un tiempo para ilustrar lo que hace internamente: https://gist.github.com/gaearon/1d19088790e70ac32ea636c025ba424e .

Sí, quiero decir que si exporta mapStateToProps y dispatchStateToProps, eso sería mejor. No quiero probar que connect() (el colaborador en mi prueba) funciona, definitivamente no. Entonces esa sería una forma clara de hacerlo, exportar esos dos métodos:

ejemplo-contenedor-spec.js

describe('testing mapPropsToState directly', () => {
    it.only('returns expected state', () => {
        const state = {firstName: 'Dave'};
        const output = mapStateToProps(state);

        expect(output.firstName).to.equal('Dave');
    });
});

Envase

import React from 'react';
import { connect } from 'react-redux'
import Example from './Example';

export const mapStateToProps = (state) => ({
    firstName: state.firstName
})

export default connect(mapStateToProps)(Example);

Presentación

import React, { Component } from 'react';

export default class Example extends Component {
    render(){
        return (
            <div className='example'>
                {this.props.firstName}
            </div>
        )
    }
}

Por otra parte, también podría hacer algo como esto con renderizado _shallow_ y aún sumergirse en el componente secundario:

ejemplo-contenedor-spec.js

it('persists firstName to presentation component', () => {
        var state = {firstName: "Dave"};
        const container = mount(<ExampleContainer store={fakeStore(state)}/>),
            example = container.find(Example);

        expect(example.length).to.equal(1);
        expect(example.text()).to.equal(state.firstName);
    });

Envase

import React from 'react';
import { connect } from 'react-redux'
import Example from './Example';

const mapStateToProps = (state) => ({
    name: state.firstName
})

export default connect(mapStateToProps)(Example);

Presentación

import React, { Component } from 'react';

export default class Example extends Component {
    render(){
        return (
            <div className='example'>
                {this.props.firstName}
            </div>
        )
    }
}

Otra forma más de hacer esto superficialmente es hacer una doble superficialidad. Superficial en el padre superficial reducirá el componente secundario que está envolviendo, algo como esto:

it('get child component by double shallow()', () => {
        const container = shallow(<ExampleContainer store={fakeStore({})}/>),
            presentationalChildComponent = container.find(Example).shallow().find('.example');

        expect(presentationalChildComponent).to.have.length(1);
    });

Entonces, es solo otra manera, está verificando el resultado de su componente de presentación.

De cualquier manera funcionaría ... sin embargo, como dijiste, la exportación de mapStateToProps es probablemente la mejor, ya que eso es todo lo que me importa probar en términos de configuración del estado de prop.

Me doy cuenta de que eres absolutamente un experto en pruebas y yo no, pero parece que te estás volviendo un poco paranoico con las cosas :)

no, no lo soy, es importante hacer pruebas correctas , lo que significa que es importante tener buenas pruebas que no sean frágiles y que proporcionen valor . En primer lugar, nadie es un _experto_. Todo el mundo está aprendiendo continuamente, algunos simplemente no quieren admitirlo debido a sus egos. Se llama Software Craftsmanship (también conocido como Profesionalismo).

Probar solo el comportamiento y no probar a los colaboradores (connect() en sí) como dijiste es importante. Descubrir eso por primera vez y probar bien es importante. Es importante asegurarse de que no está probando a los colaboradores y eso a veces requiere una discusión entre los compañeros desarrolladores.

Puede tener pruebas frágiles y es importante no tener pruebas frágiles. Las pruebas deben tomarse en serio, y eso significa verificar continuamente si lo estás haciendo bien. Simplemente estoy tratando de averiguar mi flujo. I TDD, entonces, cómo empiezo, es importante, y las buenas pruebas son importantes con eso. Por lo tanto, nadie debe sentirse "paranoico" cuando busca comprender las diferentes formas de probar los componentes de React.

Nuevo React Test Repo Próximamente...
De hecho, pronto compartiré un repositorio que muestra varios estilos, combinaciones y enfoques para probar react-redux. Creo que ayudará a la gente porque no soy el único que piensa en esto. Una y otra vez, ve personas que hacen las mismas preguntas sobre la prueba del contenedor redux y otros componentes. Los documentos de Redux ni los documentos de reacción-redux hacen justicia en eso, les falta documentación en el área de prueba de la OMI. Solo araña la superficie, y necesitamos un buen repositorio que muestre varios estilos sobre cómo abordar las pruebas de React, así que lo publicaré pronto.

If anyone would like to contribute to that repo, please get a hold of me para que pueda agregarte como colaborador. Me encantaría que la gente agregara ejemplos. Me gustaría ver ejemplos con React Test Utils + mocha , con Enzyme , Ava , Jest , tape , etc.

Conclusión :

Creo que los dos enfoques que hemos discutido están bien. Pruebe directamente el método, o pruébelo como lo hice anteriormente, sumérjase en él. A veces, no desea basar sus pruebas en un método específico porque puede obtener pruebas frágiles... de ahí que la gente se molestara en el pasado con las pruebas. Entonces, si probar o no alrededor de una función, depende si eso prueba la "unidad". A veces, no desea hacer público un método, y podría ser mejor probar el contrato por encima de eso y mantener parte de él en privado. Por lo tanto, siempre es importante pensar en lo que estás haciendo a menudo cuando escribes pruebas. Nada de malo con eso. Escribir buenas pruebas no es fácil.

Eso es exactamente lo que promueve TDD, hacerlo a menudo. Para que termine con un código más delgado, mejor y menos frágil. TDD lo obliga a pensar en realizar pruebas desde el principio, no más tarde. Esa es la diferencia y por qué la gente como yo quiere entender diferentes flujos y enfoques porque tengo que hacerlo cuando TDD, me obliga a reevaluar el diseño en pequeños fragmentos a menudo, y eso significa pensar en mi enfoque de prueba a menudo.

@markerikson gracias por tus aportes, me encanta hablar de pruebas. Buen material hombre. ¡No estaba cuestionando su experiencia, solo que en realidad no había intentado probar mapStateToProps directamente yo mismo! No te va mal por no ser tester ;). Simplemente soy nuevo en redux, tan simple como eso, así que tendré preguntas que no solo "tocan la superficie". Deberíamos discutir las pruebas a un nivel más bajo para que las personas entiendan lo que están haciendo, cómo pueden hacerlo y si se están beneficiando de su enfoque. Para hacer eso, debe comprender connect, react-redux, proveedor en un nivel inferior ... para que sepa cómo "solo probar el comportamiento". No he visto a muchas personas exportar mapStateToProps , lo que también me resulta interesante... y me hace preguntarme por qué no.

WeDoTDD.com
Por cierto, para cualquier persona interesada, inicié WeDoTDD.com el año pasado (escrito en React por cierto). Si un equipo específico (todos los desarrolladores en un equipo en particular) TDD, o toda su empresa (algunas empresas tienen desarrolladores donde todos TDD, especialmente empresas de consultoría), comuníquese conmigo en slack.wedotdd.com

Actualizar.

después de jugar más probando componentes conectados y reflexionando sobre lo que funciona bien para mí, ahora estoy 100% de acuerdo con la declaración de @markerikson aquí:

si realmente siente que necesita probar un componente conectado, debería poder renderizar sin necesidad de preocuparse por el proveedor o el contexto ni nada.

No he visto ninguna necesidad de introducir <Provider /> en mis pruebas y parece que estás probando a un colaborador si lo haces cuando el punto no es probar que <Provider /> está funcionando. Debe probar que el comportamiento en su componente funciona y no importa si su contenedor obtiene la tienda a través del contexto. Simplemente pase la tienda como accesorio... la conexión solo necesita alguna forma de llegar a la tienda, no importa cómo (contexto o accesorios)... deshágase del proveedor por completo en sus pruebas.

Tampoco veo la necesidad de usar la opción de contexto de la enzima, que es otra forma de conservar la tienda a través del contexto de React. Olvídese del contexto de reaccionar por completo en sus pruebas, es una hinchazón totalmente innecesaria y hace que sus pruebas sean más complejas y mucho más difíciles de mantener y leer.

Pruebe su función pura mapStateToProps directamente exportándola en su archivo contenedor. Estoy usando una combinación de pruebas mapStateToProps + también un par de pruebas que prueban mi contenedor superficialmente, verificando que el componente de presentación al menos recibe los accesorios que espero y luego me detengo allí con las pruebas de accesorios. También he decidido no hacer doble profundidad para las pruebas de componentes de mi contenedor. TDD me informa que, al intentar hacerlo, las pruebas se vuelven demasiado complicadas y probablemente esté haciendo algo mal. Ese "incorrecto" es "no duplicar cosas poco profundas para las pruebas de componentes de su contenedor". En su lugar, cree un nuevo conjunto de pruebas de componentes de presentación que prueben la salida de sus componentes de presentación de forma independiente.

Me alegra saber que tienes las cosas resueltas. (FWIW, mis comentarios fueron en gran parte en respuesta a sus preguntas sobre "costuras" y "excavar en accesorios", que realmente parecían llegar a niveles innecesarios de detalle con poco o ningún beneficio).

Usar un <Provider> /context _is_ será necesario si está haciendo una representación completa de un componente que representa otros componentes conectados, ya que esos niños conectados buscarán la tienda en contexto.

Si tiene sugerencias para mejorar los documentos actuales de Redux sobre las pruebas, presente un PR.

@markerikson buen punto en el montaje. Sin embargo, solo haría un montaje si necesitara probar el ciclo de vida del componente que estoy probando. No lo usaría para profundizar en los componentes secundarios... eso ya no calificaría como una prueba de unidad, y es básicamente una prueba de integración y estaría acoplando esa prueba a los detalles de implementación cuando debería probar esos componentes secundarios en su propio de forma aislada, no a través de un componente principal.

De todos modos buen material, gracias!

Correcto, solo digo que si _sí_ usa mount para verificar el ciclo de vida del componente en cuestión, y ese componente representa otros componentes conectados, entonces _necesitará_ que la tienda esté disponible en contexto para esos componentes anidados para evitar errores cuando se procesa el componente que se está probando.

(editar: aparentemente me estoy repitiendo, lo siento, los peligros de responder preguntas en varios lugares sin siempre volver a un hilo, o incluso desplazarme un poco hacia arriba).

ah ok lo tengo! si no habia pensado en eso...

¡Gracias por todos los comentarios aquí, @markerikson y @dschinkel! Son muy útiles. Ahora he decidido exportar mi componente no conectado y probarlo como lo haría con cualquier otro, y también probar mi mapStateToProps y mapDispatchToProps por separado. Sin embargo, hay un caso que estas pruebas no detectan, y es cuando map(State/Dispatch)ToProps en realidad no pasan a la llamada a connect . Ejemplo:

import React from 'react';
import { connect } from 'react-redux';

export const mapStateToProps = ({ name }) => ({ name });

export const Example = ({ name }) => <h1>{name}</h1>;

export default connect({ name } => ({ name: firstName }))(Example); // Whoops, didn't actually pass in `mapStateToProps`.

Esto probablemente nunca suceda en la práctica, pero podría, especialmente porque los linters no reportarían mapStateToProps como una variable no utilizada ya que se está exportando.

@danny-andrews ¿Podría ser más específico sobre cómo probar su función mapDispatchToProps?

@leizard

Ya no pruebo mapDispatchToProps o mapStateToProps directamente. He cambiado de opinión desde este hilo y aconsejo no hacerlo.

Además, al probarlos, se encontrará con el problema de @danny-andrews, pero el problema se trata más de buenas pruebas y tratar de exponer esas dos funciones no es una buena idea. Se acabó la prueba. Debe probar esos dos métodos indirectamente probando el comportamiento o probando los accesorios a través de su contenedor poco profundo. Descubrí que no hay razón para tratar de exponer esos métodos privados y ahora me di cuenta de que también era una mala práctica de prueba intentar hacerlo.

Así que no estoy de acuerdo con @markerikson , pruebe a través de su componente conectado.

Encontrar la API/contrato correcto para probar y no sobre probar, esa es la clave bebé :).

@dschinkel

Debería probar esos dos métodos indirectamente probando el comportamiento...
Así que no estoy de acuerdo con @markerikson , pruebe a través de su componente conectado.

¿Está sugiriendo que debería renunciar por completo a exportar el componente no conectado y solo probar el componente conectado? Creo que eso es "exceso de pruebas". Esto vincula sus pruebas innecesariamente con los detalles de implementación del componente que está probando. Porque la funcionalidad del componente no conectado (si tiene alguno, es otra lata de gusanos) no tiene ninguna relación con el lugar de donde recibe sus accesorios. Tal vez desee convertir ese componente en un componente puramente de presentación en el futuro. Si prueba el componente conectado, tendrá que cambiar todas sus pruebas para no inyectar más una tienda sino pasar los accesorios directamente. Si probó el componente no conectado, no tiene que hacer nada más que eliminar las pruebas específicas del componente conectado (más información a continuación).

Sin embargo, estoy de acuerdo en que no debe probar directamente mapStateToProps / mapDispatchToProps . Exponer esos métodos privados simplemente con fines de prueba siempre me pareció un olor a código. De hecho, creo que esta es la única vez que debe probar el componente conectado. Entonces, para resumir:

  1. Exportar componente no conectado y componente conectado
  2. NO exporte mapStateToProps o mapDispatchToProps
  3. Pruebe la lógica de todos los componentes a través del componente no conectado
  4. Solo pruebe el componente conectado cuando pruebe la interacción con redux (los accesorios de datos se pasan desde el lugar adecuado en la tienda/los accesorios de acción se asignan a los creadores de acciones adecuados, etc.).

La única sobrecarga adicional al hacer el número 4 es que debe desconectar la tienda redux para pasarla a su componente conectado. Sin embargo, esto es bastante fácil, ya que su API tiene solo tres métodos.

Método MapStateToProps

TestContainer.jsx:

import { connect } from 'react-redux';

export const TestContainer = ({ permissions }) => (permissions['view-message'] ? 'Hello!' : null);

export const mapStateToProps = ({ auth }) => ({ permissions: auth.userPermissions });

export default connect(mapStateToProps)(TestContainer); // Ewww, exposing private methods just for testing

TestContainer.spec.jsx:

import React from 'react';
import { shallow } from 'enzyme';

import TestContainer, { mapStateToProps } from './TestContainer';

it('maps auth.userPermissions to permissions', () => {
  const userPermissions = {};

  const { permissions: actual } = mapStateToProps({
    auth: { userPermissions },
  });

  expect(actual).toBe(userPermissions);
});

Nuevo método

TestContainer.jsx:

import { connect } from 'react-redux';

export const TestContainer = ({ permissions }) => (permissions['view-message'] ? 'Hello!' : null);

const mapStateToProps = ({ auth }) => ({ permissions: auth.userPermissions });

export default connect(mapStateToProps)(TestContainer);

TestContainer.spec.jsx:

import React from 'react';
import { shallow } from 'enzyme';

import TestContainer, { TestComponent } from './TestContainer';

it('renders TestComponent with approriate props from store', () => {
  const userPermissions = {};
  // Stubbing out store. Would normally use a helper method. Did it inline for simplicity.
  const store = {
    getState: () => ({
      auth: { userPermissions },
    }),
    dispatch: () => {},
    subscribe: () => {},
  };
  const subject = shallow(<TestContainer store={store} />).find(TestComponent);

  const actual = subject.prop('permissions');
  expect(actual).toBe(userPermissions);
});

sí, eso es lo que digo , no exporto ni pruebo mapStateToProps , se acabó la prueba. Prueba a través de la API . La API aquí es el Contenedor .

@danny-andrews

Pruebe la lógica de todos los componentes a través del componente no conectado

¿Puedes citar algunos ejemplos?

Me refiero a lo mismo que usted cuando dijo: "Debería probar esos dos métodos indirectamente probando el comportamiento"

Podría ayudar a aclarar los términos: cuando digo "componente no conectado", me refiero al componente sin procesar que se incluye en la llamada a connect y cuando digo "componente conectado" me refiero al componente resultante que se devuelve desde la llamada a connect . Creo que "componente conectado" y "contenedor" son sinónimos. De mi arriba:

// Unconnected component. Test all component logic by importing this guy.
export const TestComponent = ({ permissions }) => (permissions['view-message'] ? 'Hello!' : null);

const mapStateToProps = ({ auth }) => ({ permissions: auth.userPermissions });

// Connected component (container). Only test interaction with redux by importing this guy.
export default connect(mapStateToProps)(TestComponent);

Algunas personas preferirían dividirlos por completo y hacer que sus "contenedores" sean solo llamadas a connect que envuelven un componente puramente de presentación. En ese caso, los números 1 y 3 de la lista desaparecen y las únicas pruebas que debe escribir son las que verifican la interacción con redux (número 4).

Planeo escribir una publicación de blog ENORME sobre esto. Estoy ocupado en este momento en el código, pero responderé más tarde.

Después de leer este hilo varias veces, llegué a la conclusión de que se recomiendan 3 enfoques:

  1. Exporte mapDispatchToProps y mapStateToProps y pruébelos por separado
  2. Realice una renderización superficial del componente del contenedor y pruebe que el cableado es correcto
  3. Use selectores en lugar de mapStateToProps y creadores de acciones en lugar de mapDispatchToProps y pruébelos por separado; escriba pruebas utilizando los creadores de acciones, los reductores y los selectores juntos para asegurarse de que todo el flujo funcione.

Al final, supongo que todas las opciones son válidas, pero tienen sus pros y sus contras. El "más puro" es probablemente el segundo, pero también implica la mayor parte del trabajo. Mi preferencia personal en realidad iría hacia la opción 3.
Escribí con más detalle sobre esto en mi blog , donde también tengo algunos ejemplos de código.

@ucorina
En el mundo actual de los módulos ES6, cada archivo se considera un módulo envuelto en su propio alcance. Exportar es su API del pequeño módulo y se siente muy mal exportar métodos, solo para que pueda probarlos. Es similar a cómo haría públicos los métodos privados, solo para probarlos.

Es por eso que me inclino por el enfoque @dschinkel y no exporto mapDispatch y mapState, porque para mí son una implementación privada del componente conectado.

Sin embargo, no estoy seguro de qué hacer con un componente, que solo envuelve otro componente alrededor de una conexión.

¿Alguna vez escribiste la entrada del blog? @dschinkel le encantaría leerlo

@AnaRobynn

Sí, estoy de acuerdo contigo en que simplemente exportar mapDispatch y mapState se siente mal, y probablemente no lo usaría yo mismo, sin embargo, es una opción válida, aunque menos "pura". . Lo dejé allí como una posible opción también porque Dan Abramov sugirió esta técnica exacta aquí: https://github.com/reduxjs/react-redux/issues/325#issuecomment -199449298.

Para responder a su pregunta de "qué hacer con un componente, que solo envuelve otro componente alrededor de una conexión", diría que simplemente no lo pruebe. No hay una lógica comercial relacionada con su aplicación y no desea probar la implementación connect , que ya se probó en la biblioteca react-redux .

Si aún desea probar de todos modos, para probar su código en el futuro, siempre puede seguir el segundo enfoque que describo aquí, que está más cerca de lo que describe @dschinkel .

@ucorina Exactamente, ¡creo que la opción es la que yo elegiría también! Gracias por explicar más a fondo tu respuesta. Es una publicación de blog muy bien escrita. ¡Felicidades!

@ucorina Perdón por molestar de nuevo, pero después de dormir un rato no estoy seguro de estar completamente de acuerdo con la opción 2 tampoco. Aunque no estoy seguro. Tampoco estoy seguro del beneficio de probar mapStateToProps en absoluto.

¿Crees que está bien que la "Opción 2", indirectamente, también pruebe a los creadores de acciones? ¿Y también probando selectores indirectamente? ¿Eso es algo bueno?
No estoy 100% convencido por eso, pero también estoy confundido acerca de toda la información (errónea) que hay. Hay tantas ideas diferentes para probar.

  1. Solo use selectores dentro mapStateToProps

    • burlarse de ellos durante las pruebas de contenedores si es necesario

    • Pruebe los selectores por separado.

    • Solo use creadores de acciones dentro mapDispatchToProps

    • burlarse de ellos durante las pruebas de contenedores si es necesario.

    • Prueba los creadores de acciones por separado.

    • Superficial el componente conectado

    • afirmar que está representado

    • pruebe cualquier lógica adicional que haya tenido que agregarse a mapStateToProps , mapDispatchToProps o mergeProps y que no se haya probado en otro lugar. (despachos condicionales, selectores que toman argumentos de ownProps , etc.)



      • en este caso, solo está probando que los simulacros se llamaron la cantidad correcta de veces y con los argumentos correctos.



Alternativa:

  • Estado de tienda simulado para probar contenedor+selectores juntos
  • Simulacros de llamadas externas a la API para probar el contenedor y las acciones juntas

@AnaRobynn Creo que el segundo enfoque de @ucorina es el correcto.

Mucho de lo que escucho sugiere:

  1. Debe exponer mapDispatchToProps y probarlo directamente
  2. No necesita probar su llamada a connect() porque connect() ya está probado

Con respecto a 1, no creo que sea una buena práctica exponer las partes internas para probar. Sus pruebas A: ahora asumen una estructura interna, y B: debería probar cómo se usarán realmente.

Con respecto a 2, _absolutamente_ deberías probar algo que llame a una biblioteca externa. Este es uno de los puntos débiles en cualquier aplicación para romper. Con cualquier biblioteca, todavía es posible que se introduzca un cambio importante en una actualización de versión menor/parche, y _quieres_ que tus pruebas fallen rápidamente.

¿Crees que está bien que la "Opción 2", indirectamente, también pruebe a los creadores de acciones? ¿Y también probando selectores indirectamente? ¿Eso es algo bueno?

Sí, la cobertura de prueba redundante es algo bueno.

@philihp @dougbacelar
Creo que me relacioné un poco con las pruebas de aplicaciones React. Mis creencias y pensamientos actuales sobre el tema:

  1. No exponga mapStateToProps y mapDispatchToProps si no necesita esas asignaciones en varios componentes. Exportarlos solo para probar es un antipatrón.
  2. Actualmente veo contenedores como colaboradores (funciones que delegan otras funciones y esas otras funciones realizan la lógica real).
    ¿Que significa eso?
  3. Los contenedores no realizan ninguna lógica.
  4. La lógica para seleccionar un estado es para funciones de selector (que pueden ser puramente probadas por unidad)
  5. La lógica de las acciones está oculta en los creadores de acciones (lo que podría depender de si está utilizando redux-thunk o no, son colaboradores por sí mismos)
  6. La vista renderizada es otra función (que potencialmente tiene otra función de colaborador o simplemente renderiza cosas

=> La configuración de la prueba es bastante sencilla cuando usas las bibliotecas de prueba adecuadas (recomiendo testdouble.js).
=> Simule su selector y acciones a través de testdouble.js (usando td.when() )
=> Renderice superficialmente su componente contenedor, dive() una vez para obtener acceso a los métodos del componente de vista
=> Dado el conjunto de reglas por td.when() afirmar si el componente se comporta correctamente

No es necesario inyectar alguna biblioteca fakeStore, está bien desconectar la tienda.

Ejemplo:

/** <strong i="27">@format</strong> */

import React from 'react';
import { shallow } from 'enzyme';

function setup(props) {
  const HasOrganization = require('./HasOrganization').default;
  const defaultProps = {
    store: {
      subscribe: Function.prototype,
      getState: Function.prototype,
      dispatch: Function.prototype,
    },
  };
  const container = shallow(<HasOrganization {...defaultProps} {...props} />);
  return {
    container,
    wrapper: container.dive(),
  };
}

describe('<HasOrganization /> collaborations', () => {
  let getCurrentOrganizationId;
  beforeEach(() => {
    getCurrentOrganizationId = td.replace('../containers/App/userSelectors')
      .selectCurrentOrganizationId;
  });

  test('not render anything when the id is not valid', () => {
    const Dummy = <div />;
    td.when(getCurrentOrganizationId()).thenReturn(null);
    const { wrapper } = setup({ children: Dummy });

    expect(wrapper.find('div').length).toBe(0);
  });

  test('render something when the id is valid', () => {
    const Dummy = <div />;
    td.when(getCurrentOrganizationId()).thenReturn(1);
    const { wrapper } = setup({ children: Dummy });

    expect(wrapper.find('div').length).toBe(1);
  });
});

¿Pensamientos?

@AnaRobynn ¡ Muy de acuerdo con los puntos 1 y 2!

Mis contenedores suelen ser muy sencillos:

// imports hidden

const mapStateToProps = (state, { userId }) => ({
  isLoading: isLoading(state),
  hasError: hasError(state),
  placeholders: getPlaceholders(userId)(state), // a list of some sort
  shouldDoStuff: shouldDoStuff(state),
});

const mapDispatchToProps = {
  asyncActionPlaceholder,
};

export const mergeProps = (stateProps, dispatchProps, ownProps) => {
  return {
    ...stateProps,
    ...dispatchProps,
    ...ownProps,
    onMount: () => {
      if (stateProps.shouldDoStuff) {
        dispatchProps.asyncActionPlaceholder(ownProps.userId);
      }
    },
  };
};

export default compose(
  connect(
    mapStateToProps,
    mapDispatchToProps,
    mergeProps
  ),
  callOnMount(props => props.onMount())
)(SampleComponent);

entonces lo haría

  1. espiar todos los selectores y creador de acciones
  2. prueba getPlaceholders se llamó al espía con el ID de usuario correcto
  3. afirmar que SampleComponent se representó con los accesorios correctos
  4. llama al accesorio onMount desde SampleComponent y verifica que envía la acción simulada cuando shouldDoStuff===true
  • Cosas como la representación condicional de cosas dependiendo de los accesorios que probaría en una prueba separada por SampleComponent
  • También prefiero usar redux-mock-store para burlarme de la tienda como configureMockStore([thunk])() pero creo que está bien no hacerlo...

TL; DR Básicamente, solo se llamaron a los selectores de prueba con los argumentos correctos, las acciones simuladas se enviaron con éxito (si dependen de alguna lógica) y que el componente secundario se representa con los accesorios correctos

@philihp

  • Estoy muy en contra de probar bibliotecas externas, prefiero burlarme de todo y probar cosas de forma aislada.
  • Me siento seguro al confiar en que las pruebas integrales detectarán cualquier problema importante causado por las actualizaciones de la biblioteca.
  • La razón por la que no me gusta probar contenedores+selectores+creadores de acciones juntos es que tendrá que volver a probar los mismos selectores y creadores de acciones para cada otro contenedor que los reutilice. En ese punto, es mejor que agregue más casos de prueba a las pruebas unitarias.

@dougbacelar ¿Puede dar un código de prueba de muestra para probar el componente que mostró? ¿Estás usando enzima? ¿Algo más? ¿Poco profundo? ¿Montar?

@dougbacelar

Estoy muy en contra de probar bibliotecas externas, prefiero burlarme de todo y probar cosas de forma aislada.

Simulacro cuando lo necesites, pero si puedes usar algo real, ¿por qué no? La simulación es útil cuando ciertas llamadas son lentas o tienen efectos secundarios como solicitudes de red.

La razón por la que no me gusta probar contenedores+selectores+creadores de acciones juntos es que tendrá que volver a probar los mismos selectores y creadores de acciones para cada otro contenedor que los reutilice. En ese punto, es mejor que agregue más casos de prueba a las pruebas unitarias.

, prueba tus selectores, creadores de acciones de forma independiente. Pruébelos a fondo con muchas entradas esperadas.

, también pruebe sus contenedores. Si eso significa que brindan cobertura superpuesta sobre selectores y creadores de acciones, eso no es malo.

Esto es lo que quiero decir...

import React from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { saveColor } from '../actions/save';
import { selectColors } from '../reducers/colors.js';
import ColorButtons from '../components/ColorButtons';
const ColorButtons = ({ colors, onClick }) => (
  <div>
    {colors.map(color => {
      <button type="button" key={color} onClick={onClick(color)}>{color}</button>
    })}
  </div>
);
ColorButtons.propTypes = {
  colors: PropTypes.arrayOf(PropTypes.string),
  onClick: PropTypes.func.isRequired,
};
const mapStateToProps = state => ({
  colors: selectColors(state.colors),
});
const mapDispatchToProps = dispatch => ({
  onClickColor: color => () => {
    dispatch(saveColor({ color }));
  },
});
export default connect(
  mapStateToProps,
  mapDispatchToProps
)(ColorButtons);
import React from 'react';
import { shallow } from 'enzyme';
import configureStore from 'redux-mock-store';
import ColorButtons from '../ColorButtons';
import { saveColor } from '../../actions/save';
import { selectColors } from '../../reducers/colors.js';
const buildStore = configureStore();
describe('<ColorButtons />', () => {
  let store;
  let wrapper;
  const initialState = { colors: ['red', 'blue'] };
  beforeEach(() => {
    store = buildStore(initialState);
    wrapper = shallow();
  });
  it('passes colors from state', () => {
    expect(wrapper.props().colors).toEqual(selectColors(initialState.colors));
  });
  it('can click yellow', () => {
    const color = 'yellow';
    wrapper.props().onClick(color)();
    expect(store.getActions()).toContainEqual(saveColor({ color }));
  });
});

Aquí, esto no está probando todos los diferentes parámetros que podrían obtener selectColors y saveColor ; está probando que cuando se crea <ColorButtons /> , obtiene las propiedades que esperamos que obtenga. Absolutamente prueba esos en otra prueba.

import { selectColors } from '../colors.js';
describe('selectColors', () => {
  it('returns the colors', state => {
    expect(selectColors(['red'])).toEqual(['red'])
  })
  it('returns an empty array if null', state => {
    expect(selectColors(null)).toEqual([])
  })
  it('returns a flattened array', state => {
    expect(selectColors(['red', ['blue', 'cyan']])).toEqual(['red','blue','cyan'])
  })
})

Estoy muy en contra de probar bibliotecas externas, prefiero burlarme de todo y probar cosas de forma aislada.

Simulacro cuando lo necesites, pero si puedes usar algo real, ¿por qué no? La simulación es útil cuando ciertas llamadas son lentas o tienen efectos secundarios como solicitudes de red.

En desacuerdo, en parte.
Nunca me burlo de las bibliotecas externas (no se burle de lo que no posee), pero escribo un envoltorio, por ejemplo, axios. En mi prueba de colaboración, puedo simular todas las funciones y asegurarme de que todo esté conectado correctamente.

En mis pruebas de colaboración siempre me burlo de módulos, funciones, etc.
Estas funciones llamadas por su colaborador pueden probarse fácilmente.

Esto es lo que quiero decir...

También en desacuerdo.
Está probando implícitamente sus reductores y selectores aquí. Si eso es lo que quiere hacer bien, pero prefiero simular estas funciones y simplemente verificar si se llaman correctamente. El resto lo manejan los reductores/selectores.

Este hilo está repitiendo los puntos hechos en https://martinfowler.com/articles/mocksArentStubs.html , pruebas clásicas y simuladas

Acordado. Cierre.

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

Temas relacionados

CellOcean picture CellOcean  ·  3Comentarios

benoneal picture benoneal  ·  3Comentarios

ramakay picture ramakay  ·  3Comentarios

elado picture elado  ·  3Comentarios

jbri7357 picture jbri7357  ·  3Comentarios