reactjs validation testing resiliency

Aplicación ReactJS: resistencia frente a fallas rápidas



validation testing (3)

Estoy en el medio del desarrollo de una aplicación React y este es el enfoque que utilicé para mis componentes: valido los accesorios que espero recibir con la validación PropTypes pero sigo asignando valores predeterminados para evitar que se rompe si algo sale mal con los datos recibidos.

Recientemente me han dicho que no debemos hacer eso, que los accesorios son lo que esperamos de los padres y si no se respeta el contrato para que se rompa el componente.

¿Qué enfoque es correcto y cuáles son los pros y los contras?

Algunas de mis consideraciones como alimento para el pensamiento ...

Siguiendo mi enfoque inicial, en las pruebas pruebo explícitamente los valores predeterminados que pasan al componente bajo prueba algunos datos no válidos y espero que todavía se imprima una instantánea válida. Las pruebas no fallan debido a algunos datos incorrectos, pero imprimo las advertencias de validación de PropTypes (eso podría transformarse en errores si lo desea, creo) o silenciarse burlándose de ellos en la prueba).

Estas advertencias, tanto en las pruebas como en la aplicación real, son más concisas y claras que solo ver un error que dice "no se puede leer ''someProp'' desde indefinido" o similar (y dejar que React rinda el ciclo). Las validaciones de propType le dicen directa y claramente lo que hizo mal (pasó el tipo incorrecto como accesorio, faltaba el accesorio por completo, etc.).

Usando el segundo enfoque, las pruebas fallan porque la aplicación se rompe. Creo que este es un buen enfoque solo si la cobertura de la prueba es realmente buena (90/100%) de lo contrario es un riesgo: podría activarse y romper casos extremos que arruinarían la reputación del producto. La refactorización o los cambios en los requisitos ocurren con bastante frecuencia y algunos casos extremos pueden terminar con datos no deseados que rompen la aplicación y no se capturaron en pruebas automáticas o manuales.

Esto significa que cuando la aplicación está activa, el código puede romperse en un componente principal debido a algunos datos incorrectos y toda la aplicación deja de funcionar, mientras que en el primer caso la aplicación es resistente y simplemente muestra algunos campos vacíos de manera controlada.

Pensamientos?

Sigue un ejemplo simplificado:

Componente de reacción

import React from ''react''; import PropTypes from ''prop-types''; import styles from ''./styles.css''; export const App = ({ person : { name, surname, address, subscription } = {} }) => ( <div style={styles.person}> <p> {person.name} </p> <p> {person.surname} </p> <p> {person.address} </p> <div> { person.subscription && <Subscription details={person.subscription} /> } </div> </div> ); // PS. this is incorrect in this example (as pointed out in an answer). Real code used inline initialization. // App.defaultProps = { // person: { subscription: undefined }, // }; App.propTypes = { person: PropTypes.shape({ name: PropTypes.string.isRequired, surname: PropTypes.string.isRequired, address: PropTypes.string, subscription: PropTypes.object, }).isRequired, };

Prueba

import React from ''react''; import { shallow } from ''enzyme''; import { mockOut } from ''testUtils/mockOut''; import { App } from ''../index.js''; describe(''<App>'', () => { mockout(App, ''Subscription''); it(''renders correctly'', () => { const testData = { name: ''a name'', surname: ''a surname'', address: ''1232 Boulevard Street, NY'', subscription: { some: ''data'' }, } const tree = shallow(<App person={testData} />); expect(tree.html()).toMatchSnapshot(); }); it(''is resilient in case of bad data - still generates PropTypes validation logs'', () => { const tree = shallow(<App person={undefined} />); expect(tree.html()).toMatchSnapshot(); }); });

ACTUALIZAR:

El enfoque principal de la pregunta es si es correcto o no asignar valores predeterminados a los accesorios marcados con isRequired (en lugar de permitir que su ausencia rompa el componente)


Recientemente me han dicho que no debemos hacer eso, que los accesorios son lo que esperamos de los padres y si no se respeta el contrato para que se rompa el componente.

Exactamente, si un accesorio en el componente es opcional, el componente (que representa la vista real) debería manejar eso, no el componente principal.

Sin embargo, puede tener una situación en la que el padre debe romperse si alguno de los componentes secundarios se rompe. Se me ocurren dos formas posibles de manejar esta situación.

  1. Al pasar el notificador de error a los componentes secundarios, si algo sale mal, el niño puede informar el error al componente principal. Pero esta no es una solución limpia porque si hay N hijos y si más de uno se rompe (o informa un error) a los padres, no tendrá ni idea y será difícil de manejar. [Esto no es efectivo en absoluto, pero escribió aquí porque Solía ​​seguir esto cuando estaba aprendiendo Reaccionar: P]

  2. Usar try/catch en el componente padre y no confiar en ningún componente hijo ciegamente y mostrar mensajes de error cuando algo sale mal. Cuando usa try/catch en todos sus componentes, puede arrojar errores de forma segura desde los componentes cuando no se cumple ningún contrato.

¿Qué enfoque es correcto y cuáles son los pros y los contras?

En mi opinión, el segundo enfoque ( try/catch componentes y error de lanzamiento cuando no se cumplen los requisitos) es válido y resolverá todos los problemas. Al escribir pruebas para el componente cuando no se pasan los accesorios, puede esperar un error al cargar el componente.

Actualizar

Si está utilizando React> 16, esta es la forma de manejar los errores.


En mi opinión, no permitiré que uno o dos atributos faltantes rompan mi aplicación. React actúa como capa de presentación en mi aplicación y creo que es muy lejos mostrar "¡Vaya! Hay algo mal" cuando no puedo encontrar una clave en un objeto. Parece un mensaje de un servidor dañado con un estado 500, pero sabemos que definitivamente no está tan mal.

Para mí, construyo algunas reglas para manejar la comunicación entre la función de render y defaultProps:

Digamos que tenemos un objeto de usuario pasado de padre:

defaultProps: { user: { avatar: { small: '''' } } }

en la función de render

render() { const { user } = this.props; // if user.avatar is not defined or user.avatar.small is empty string or undefined then we render another component we have prepared for this situation. if (!user.avatar || !user.avatar.small) { return ( // components ... ); } // normal situation return ( // components ... ); }

El ejemplo anterior es para cadenas y necesitamos diferentes implementos para otros tipos de datos.

Buena suerte.


No es correcto asignar valores predeterminados a los accesorios .isRequred través del componente defaultProps . Según los documentos oficiales :

DefaultProps se utilizará para garantizar que this.props.name tendrá un valor si no fue especificado por el componente principal. La verificación de tipo propTypes ocurre después de que se resuelven defaultProps, por lo que la verificación de tipo también se aplicará a defaultProps.

Si establece el valor de propiedad predeterminado en Component.defaultProps, nunca recibirá una advertencia si el componente principal no proporciona este accesorio.