Bienvenido, soy Miguel y esta vez les traigo este tutorial.
Como novato en la interfaz, me tomó mucho tiempo tratar de entender cómo probar mi aplicación React, especialmente para probar el componente de función con ganchos.
En este tutorial, ilustraré algunos métodos básicos para probar el componente de función desde el punto de vista de un principiante y desglosaré los problemas que encontré.
Índice
¿Por qué necesitamos probar nuestro código?
Para mí, la parte más importante de probar la aplicación React es garantizar que su aplicación funcione correctamente, independientemente de los cambios que haya realizado en otro componente o interacción realizada por el usuario.
Tal vez sea un poco trivial en un proyecto pequeño, sin embargo, cuando su proyecto se vuelve cada vez más grande, no desea probar todas las funciones manualmente, ¿verdad? Será una total pérdida de tiempo y es posible que olvide probar algunos casos especiales. Por eso necesitamos pruebas.
Configurar el entorno
Para simplificar, usemos create-react-app
para crear un proyecto de React.
$ npx create-react-app test
Después de eso, debería ver un archivo llamado App.test.js
debajo src
carpeta, ese es el archivo que escribiremos nuestra prueba en este tutorial.
Vue
, Typescript
, node
, etc.
Por otro lado, usaremos React Testing Library
para ayudarnos a probar el componente React, nos permite trabajar con nodos DOM
reales, mientras jest-dom
proporciona algunos comparadores DOM
personalizados útiles.
Las bibliotecas anteriores ya están instaladas si usa create-react-app
para iniciar su proyecto React. De lo contrario, puede instalarlos manualmente mediante los siguientes comandos:
$ npm install --save-dev jest $ npm install --save-dev @testing-library/react $ npm install --save-dev @testing-library/jest-dom
Digamos que tenemos un componente principal en App.js
que contiene un componente de encabezado, un div con contenido y un componente de botón.
Lo primero que nos gustaría hacer es probar si el componente se representa correctamente. Podemos agregar data-testid
en el atributo del elemento que queremos apuntar. Por ejemplo,
import React from 'react'; import {Header, Button} from './components' function App() { return ( <> <Header /> {/* add data-testid to target content div */} <div data-testid="content">Some Content</div> <Button /> </> ); } export default App;
En la línea 8 agregamos un data-testid.
Entonces en App.test.js
, ¡podemos empezar a escribir nuestra prueba!
import React from 'react'; import {render} from '@testing-library/react'; import App from './App'; describe('Test for App', () => { test('Test Rendering', () => { const {getByTestId} = render(<App />) expect(getByTestId('content')).toBeInTheDocument() }) })
El código anterior puede contener algo con lo que no está familiarizado. Permítanme explicar brevemente cada término.
- Test:
test
toma dos parámetros, el primero es la descripción de esta prueba y el segundo es una función que contiene la implementación de esta prueba. - Describe:
describe
es parecido atest
, puede incluir muchas pruebas en unadescribe
. (De hecho, cuando se trata dedescribe
ytest
, también tienen algún problema con el alcance, pero no entraremos en ese conocimiento en esta guía para principiantes). - Expect:
expect
es donde escribimos nuestra afirmación. ‘Esperamos’ que suceda algo. - Render: Como su nombre,
render
renderizará el componente, y también puede devolver alguna función útil comogetByTestId
,rerender
,container
etc. - GetByTestId:
getByTestId
es un función, pasamos ladata-testid
como argumento para apuntar al elemento. - ToBeInTheDocument:
toBeInTheDocument
es un comparador personalizado proporcionado porjest-dom
, le permite verificar si el elemento de destino se representa en el DOM correctamente.
Ahora puedes correr yarn test src/App.test.js
para ejecutar la prueba.
¿Qué tal probar el componente Encabezado y Botón? Veamos si podemos agregar data-testid
directamente sobre ellos.
import React from 'react'; import {Header, Button} from './components' function App() { return ( <> <Header data-testid="Header"/> {/* add data-testid to target content div */} <div data-testid="content">Some Content</div> <Button data-testid="Button"/> </> ); } export default App;
Agregue data-testid al componente Encabezado y Botón.
import React from 'react'; import {render} from '@testing-library/react'; import App from './App'; describe('Test for App', () => { test('Test Rendering', () => { const {getByTestId} = render(<App />) expect(getByTestId('content')).toBeInTheDocument() // add two more expect expect(getByTestId('Button')).toBeInTheDocument() expect(getByTestId('Header')).toBeInTheDocument() }) })
Agregue dos más de espera en App.test.js.
Correr yarn test src/App.js
de nuevo. Sin embargo, esta vez obtuvimos un error.
Podemos ver que detecta un error en la línea 9. El mensaje de error muestra que es Unable to find an element by: [data-testid=”Button”]
.
Tenga en cuenta que después de renderizar, nuestro componente se transformará en <div>
y <button>
sin data-testid
(puede encontrar el resultado renderizado en el mensaje de error en la imagen de arriba), así que aquí está la moraleja de esta historia. ¡No podemos agregar data-testid
directamente en el componente!
Afortunadamente, la solución a este problema es bastante sencilla. Solo agrega el data-testid
en la implementación del componente y la prueba pasará.
import React from 'react' const Header = () => { return(<div data-testid="Header">This is a header</div>) } export {Header}
Añade el data-testid
en la implementación del componente.
React Testing Library proporciona una función llamada fireEvent
para simular el evento web. Aquí usaré el evento de clic como ejemplo.
En el componente Botón, tengo un handleClick
función que incrementará el contador cada vez que haga clic en el botón. Y mostrará el valor del contador en el botón.
import React, {useState} from 'react' const Button = () => { const [count, setCount] = useState(0) const handleClick = () => { setCount(count => count + 1) } return(<button data-testid="Button" onClick={handleClick}>{count}</button>) } export {Button}
¿Cómo probamos si el handleClick
la función se ejecuta correctamente cuando el usuario hace clic en el botón? La respuesta es bastante sencilla.
¡Podemos simular el evento onClick! Puede agregar el siguiente código en el mismodescribes
alcance en App.test.js
test('Test onClick event', () => { const {getByTestId} = render(<App />) const button = getByTestId('Button') // check the initial value expect(button.textContent).toBe('0') // simulate the onClick event on button fireEvent.click(button) // check the value of the counter expect(button.textContent).toBe('1') })
El concepto principal es que puedes simular el evento usando fireEvent
, entonces puede hacer alguna afirmación sobre el valor del contador probando el textContent
del botón.
Para su información, Jest ofrece muchos comparadores para que los pruebes, y sus nombres son muy razonables, por ejemplo toHaveBeenCalled()
, toBe()
,toBeTruthy()
, etc.
Digamos que tenemos un botón que se dirigirá a /my_button
página después de hacer clic (aquí, usamos react-router-dom
para manejar la funcionalidad de enrutamiento).
import React from 'react' import {useHistory} from 'react-router-dom'; const Header = () => { let history = useHistory(); const handleClick = () => history.push('/my_button') return( <div data-testid="Header"> <button data-testid="routing-button" onClick={handleClick} style={{fontSize: '20px'}}>routing button </button> </div>) }
¿Cómo probamos si el history.push
la función se llama correctamente? El truco es que jest
«burlémonos» del módulo.
const mockHistoryPush = jest.fn(); jest.mock('react-router-dom', () => ({ useHistory: () => ({push: mockHistoryPush}), })); describe('Test Routing Button', () => { test('Test history', () => { const {getByTestId} = render(<App />) const routingButton = getByTestId('routing-button') fireEvent.click(routingButton) expect(mockHistoryPush).toHaveBeenCalledWith('/my_button'); }) })
Puede consultar los documentos para la explicación detallada de jest.mock()
y jest.fn()
. Básicamente, creamos una función simulada llamada mockHistoryPush
para reemplazar la función de empuje real.
Luego, nos burlamos del módulo. react-router-dom
, que significa ahora la burla react-router-dom
módulo solo tiene un método llamado useHistory
, y contiene un método push que es mockHistoryPush
que acabamos de crear antes.
Por lo tanto, podemos ver todos history.push
en nuestro componente como una nueva función simulada, y puede probar si el parámetro (su URL) es correcto usando toHaveBeenCalledWith()
emparejadores.
Jest
proporciona un método llamado useFakeTimers
para burlarse de los temporizadores, lo que significa que puede probar funciones nativas del temporizador como setInterval
, setTimeout
sin esperar el tiempo real. ¡Puedes controlar el tiempo!
En el siguiente ejemplo, tenemos un botón de tiempo de espera. Cuando haga clic en el botón, llamará handleTimeoutButton
función, espere 2 segundos y luego diríjase a /my_button
página.
import React from 'react'; import {useHistory} from 'react-router-dom'; import {Header} from './components' const App = () => { let history = useHistory() const handleTimeoutButton = () => { setTimeout(() => { history.push('/my_button') }, 2000) } return ( <div style={{height: '60vh', fontSize: '30px', display: 'flex', alignItems: 'center', justifyContent:'space-around', flexDirection: 'column' }} > <Header /> <div data-testid="content">Content Area</div> <button data-testid="timeout-button" onClick={handleTimeoutButton}>Timeout Button</button> </div> ); } export default App;
Con la función jest.useFakeTimers()
, no necesitamos esperar 2 segundos durante la prueba. Podemos controlar el tiempo llamando función jest.advanceTimersByTime
.
jest.useFakeTimers(); describe('Test Timeout Button', () => { test('Test Timeout', () => { const {getByTestId} = render(<App />) const timeoutButton = getByTestId('timeout-button') fireEvent.click(timeoutButton) expect(mockHistoryPush).not.toHaveBeenCalledWith('/my_button'); jest.advanceTimersByTime(2000); expect(mockHistoryPush).toHaveBeenCalledWith('/my_button'); }) })
Ahora apliquemos redux
al proyecto. Supongamos que tenemos un estado inicial como el siguiente
Entonces puedo usar useSelector
para obtener la información del usuario.
import React from 'react'; import {useSelector, useDispatch} from 'react-redux'; import {Header} from './components' const App = () => { const {name, gender, email} = useSelector(state => state.userInfo) return ( <div> {name ? <ul> <li data-testid="user-name">{`name: ${name}`}</li> <li>{`gender: ${gender}`}</li> <li>{`email: ${email}`}</li> </ul> : <div data-testid="user-not-found">User Not Found</div>} </div> ); } export default App;
En la situación del mundo real, es posible que necesitemos obtener el userInfo
utilizando la solicitud de API. Por tanto, necesito comprobar si el userInfo
se almacena correctamente en el reductor. Si el usuario está vacío, mostrará «Usuario no encontrado» en la pantalla.
La pregunta es ¿cómo probamos esta situación? El truco que usaremos es spyOn
y mockReturnValue
.
import * as ReactRedux from 'react-redux'; describe('Test useSelector', () => { const mockUseSelector = jest.spyOn(ReactRedux, 'useSelector'); test('Test Rendering (user found)', () => { mockUseSelector.mockReturnValue({ name: 'John', gender: 'male', email: 'example@gmail.com' }); const {getByTestId} = render(<App />) expect(getByTestId('user-name')).toHaveTextContent('John') }) test('Test Rendering (user not found)', () => { mockUseSelector.mockReturnValue({ name: '', gender: '', email: '' }); const {getByTestId} = render(<App />) expect(getByTestId('user-not-found')).toBeInTheDocument() }) })
Básicamente, spyOn
le permite rastrear el métodouseSelector
) del objeto (ReactRedux
), entonces useSelector
en App.js
ahora se convertirá en una función simulada.
Entonces, puedo usar mockReturnValue
para burlarse del valor de retorno (en este caso, el userInfo
) de esta función simulada.
Siguiendo el ejemplo anterior, supongamos que usamos un creador de acciones asíncronas (getUserInfo
) para enviar una solicitud de API cuando se procesa el componente.
import React, {useEffect} from 'react'; import {useSelector, useDispatch} from 'react-redux'; import {Header} from './components' import {getUserInfo} from './actions' const App = () => { const {name, gender, email} = useSelector(state => state.userInfo) const dispatch = useDispatch() useEffect(() => { dispatch(getUserInfo()) }, [dispatch]) return ( <div > <Header /> {name ? <ul> <li data-testid="user-name">{`name: ${name}`}</li> <li>{`gender: ${gender}`}</li> <li>{`email: ${email}`}</li> </ul> : <div data-testid="user-not-found">User Not Found</div>} </div> ); } export default App;
¿Cómo probamos si realmente enviamos la acción correcta o no? De manera similar al ejemplo anterior, podemos usar spyOn
y mockReturnValue
para burlarse de la función de envío y usar toHaveBeenCalledWith
Checker para probar si enviamos la acción correcta.
import * as ReactRedux from 'react-redux'; import * as Actions from '../actions'; describe('Test dispatch', () => { const mockDispatch = jest.fn(); const mockUseDispatch = jest.spyOn(ReactRedux, 'useDispatch'); mockUseDispatch.mockReturnValue(mockDispatch); test('Test Handle Refresh', () => { Actions.getUserInfo = jest .fn() .mockReturnValue('getUserInfo'); const {getByTestId} = render(<App />); expect(mockDispatch).toHaveBeenCalledWith('getUserInfo'); }); })
Por ejemplo, también necesitamos probar el creador y el reductor de acciones en situaciones del mundo real. De todos modos, este es un tutorial para principiantes, y espero que les resulte útil este artículo.
Espero que te haya sido de utilidad. Gracias por leer este artículo.
Añadir comentario