Probar Sus Aplicaciones Usando Jest, Testing Library, Cypress Y Supert

¡Hola a todos! En este artículo vamos a hablar sobre las pruebas. Le daré una buena descripción general de lo que es la prueba y una introducción de cómo puede implementarla en sus proyectos de JavaScript. Usaremos cuatro herramientas muy populares: Jest, Testing library, Cypress y Supertest.

Primero, hablaremos sobre qué es la prueba, por qué es una buena idea probar nuestro código y los diferentes tipos de pruebas que se pueden implementar.

Luego, presentaremos cada una de las herramientas que usaremos y, finalmente, daremos ejemplos prácticos para el código JS estándar, una aplicación React de front-end y una aplicación Node de back-end.

¡Vámonos!

¿Qué es la prueba y por qué es valiosa?

La prueba es la práctica de verificar si una pieza de software funciona como se esperaba. Esto a menudo se reconoce como control de calidad o garantía de calidad, y tiene como objetivo reducir al mínimo la cantidad de errores que llegan a la producción.

Probamos el software para identificar errores, brechas o requisitos faltantes y arreglamos esas cosas antes de enviar el código a producción.

Probar nuestro código a fondo mejora la confiabilidad de nuestro proyecto, nos ahorra tiempo de corrección de errores posteriores y, por lo tanto, reduce los costos y mejora la posibilidad de que nuestro cliente esté completamente satisfecho con nuestro producto.

BvIJ1M5-1

Aquí hay un buen video corto de Fireship que presenta algunos de los conceptos de los que hablaremos más adelante.

Diferentes tipos de pruebas

Las prácticas de prueba se pueden clasificar en diferentes tipos de acuerdo con muchos factores. Personalmente, creo que hay mucho galimatías sobre este tema, con cientos de términos que a menudo se refieren a cosas muy similares. Así que hagámoslo simple y revisemos solo los términos más populares y lo que significan.

Esto ayudará a aclarar las muchas formas en que se puede probar un software y a comprender mejor las herramientas que presentaremos más adelante.

Pruebas manuales vs automatizadas

Dependiendo de las herramientas que utilicemos para probar nuestro software, podemos clasificar las pruebas en pruebas manuales o automatizadas .

La prueba manual es la práctica de "hacer clic" y verificar manualmente todas las funciones que tiene nuestro producto, simulando lo que haría un usuario real.

Las pruebas automatizadas se realizan a través de código, escribiendo programas que verifican cómo se ejecuta nuestra aplicación.

Hay muchos marcos de prueba y bibliotecas que podemos usar para esto. Cuando se trata de pruebas funcionales (vamos a ver lo que eso significa en un segundo), la mayoría de las bibliotecas funcionan de manera similar:

  • Primero definimos qué pieza de código queremos probar.
  • Luego le proporcionamos a ese fragmento de código algún tipo de entrada o ejecutamos una acción sobre él.
  • Luego definimos qué debe hacer esa pieza de código dada la entrada/acción que realizamos.
  • Y finalmente compararemos lo que ese fragmento de código realmente hizo con lo que dijimos que debería hacer.

Si hizo lo que dijimos que debería hacer, la prueba pasó. Si no lo hizo, fracasó.

Pruebas funcionales vs no funcionales

Las pruebas funcionales se refieren a las características reales de nuestro producto . Por ejemplo, si tenemos una plataforma de blogs, las pruebas funcionales deberían garantizar que los usuarios puedan crear nuevos artículos, editar esos artículos, navegar a través de artículos escritos por otras personas, etc.

Las pruebas no funcionales se refieren a cualquier cosa que no esté estrictamente relacionada con las características principales de nuestro producto. Y que nuevamente se puede clasificar en diferentes categorías, por ejemplo:

  • Las pruebas de estrés verifican cómo responde la infraestructura al uso intensivo.
  • Las pruebas de seguridad verifican si una aplicación es vulnerable a ataques de piratería comunes.
  • Las pruebas de accesibilidad verifican si una aplicación está codificada de manera que sea accesible para personas con diferentes discapacidades.

Pruebas unitarias frente a pruebas de integración frente a pruebas de extremo a extremo

Otra forma de clasificar las pruebas es según su amplitud o exhaustividad.

Las pruebas unitarias tienen como objetivo probar funciones individuales, métodos o pequeños fragmentos de código de forma independiente. En las pruebas unitarias, se comprueban pequeños fragmentos de código de forma aislada.

Las pruebas de integración verifican cómo las piezas individuales de código interactúan entre sí y funcionan juntas. En las pruebas de integración, juntamos piezas y vemos si interactúan correctamente.

Las pruebas de extremo a extremo , también conocidas como E2E, ejecutan programas en un entorno simulado que emula el comportamiento real del usuario. Teniendo un sitio web como ejemplo, nuestro código se abriría en un navegador real y todas las funciones se ejecutarían de la misma manera que las usaría un usuario. Las pruebas E2E se parecen mucho a las pruebas manuales en ese sentido, pero totalmente automatizadas.

La prueba E2E es el tipo más amplio o completo de estos tres, ya que evalúa características y comportamientos completos, no partes específicas de nuestro código.

Pruebas de caja blanca, caja negra y caja gris

La última clasificación que vamos a ver depende de cuánto se centren nuestras pruebas en los detalles de implementación o la experiencia del usuario.

Digamos que tenemos un sitio web simple con un botón que, cuando se hace clic, abre un modal. En nuestro código, el botón tiene un detector de eventos de clic que ejecuta una función. Esa función cambia la clase CSS de nuestro elemento HTML modal, y eso hace que el modal se represente en la pantalla.

Hablamos de pruebas de " caja blanca " cuando probamos los detalles de implementación . Siguiendo el ejemplo, bajo este paradigma podríamos probar que el clic del botón ejecuta la función correspondiente, y que después de la ejecución de la función, la clase CSS de nuestro elemento modal cambia en consecuencia.

Otra forma de hacer esto es olvidarse de la implementación y simplemente verificar si el modal se representa después de hacer clic en el botón. No nos importa cuál sea la clase CSS, o si la función correspondiente se ejecuta o no. Solo nos enfocamos en probar lo que el usuario debe percibir. Esa es la prueba de " caja negra ".

Y, como habrá adivinado, la prueba de "caja gris" es solo una combinación de las dos anteriores.

Una última cosa que mencionar aquí es que estos diferentes tipos de pruebas no son necesariamente excluyentes entre sí. Quiero decir, pueden y, a menudo, se implementan al mismo tiempo en los mismos proyectos.

Es muy común tener pruebas tanto manuales como automatizadas, pruebas funcionales y no funcionales, pruebas unitarias y E2E... La idea siempre será tratar de anticipar y resolver el mayor número posible de problemas en un tiempo y esfuerzo razonables.

Cuándo probar

Esto puede parecer una pregunta simple al principio, pero en realidad también hay diferentes enfoques para esto.

A algunas personas les gusta probar su aplicación una vez que se ha desarrollado por completo. A otros les gusta escribir pruebas al mismo tiempo que codifican la aplicación y prueban cada función a medida que se desarrolla.

A otros les gusta escribir primero las pruebas antes que nada, definiendo de esta manera los requisitos mínimos que debe cumplir el programa. Y luego codifican la aplicación de una manera que pasa esas pruebas lo más rápido posible (esto se llama desarrollo basado en pruebas o TDD ).

Una vez que haya desarrollado una aplicación o una función completa y tenga un conjunto de pruebas (un conjunto de pruebas es un grupo de pruebas que verifican una función particular o una aplicación completa), otra práctica común es ejecutar sus pruebas cada vez que haga cualquier tipo de modificación en el código base, para verificar que nada se rompa.

Por último, si tiene un sistema CI/CD , es común automatizar la ejecución de pruebas antes de cualquier implementación. De modo que si alguna prueba falla, la implementación se detiene y se envía algún tipo de alerta (lo que, por supuesto, siempre es mejor que ver que tu aplicación se incendia en la producción 🔥😱).

Al igual que con los tipos de prueba, es común probar las aplicaciones en diferentes momentos. Cada empresa normalmente tiene su propio programa de pruebas o práctica a seguir, adaptado a sus necesidades.

Nuestro conjunto de herramientas

Bien, ahora que tenemos una idea más clara de lo que son las pruebas y los tipos de pruebas que podemos realizar, revisemos las herramientas que usaremos en nuestros ejemplos.

Como se mencionó anteriormente, hay muchas bibliotecas diferentes para elegir para ejecutar nuestras pruebas. Elegí estos cuatro porque son algunos de los más populares cuando se trata de aplicaciones de JavaScript, pero sé que hay más opciones. Nombraré alternativas para la mayoría de las herramientas que usaremos en caso de que desee investigar más. 😉

que es broma

Jest es un corredor de pruebas de JavaScript. Un corredor de prueba es una pieza de software que le permite ejecutar pruebas para evaluar su aplicación. Es un proyecto de código abierto mantenido por Meta (anteriormente Facebook), y fue de código abierto por primera vez en 2014.

Comentario al margen: cada vez que digo "corredor de pruebas" me imagino esto. ¿Soy el único? 🤔

8gTI-1

¡Corredor de pruebas, no Blade Runner!

De todos modos... puedes usar Jest en proyectos que usan Babel , TypeScript , Node.js , React , Angular , Vue.js , Svelte y otras tecnologías también. Puede instalar Jest a través de NPM como cualquier biblioteca y requiere muy poca configuración para comenzar.

Jest viene instalado de forma predeterminada al configurar aplicaciones React con create-react-app .

Jest a menudo también se denomina marco de prueba, ya que viene con muchas otras características integradas además de solo ejecutar pruebas (que no es el caso con todos los ejecutores de prueba). Algunas de esas características son:

  • Biblioteca de afirmación: Jest viene con muchas funciones y métodos integrados que puede usar para afirmar su código (afirmar básicamente significa verificar si una parte del código se comporta como se espera).
  • Prueba de instantáneas: Jest le permite usar instantáneas, que son una forma de capturar un objeto grande y almacenarlo en la memoria para que luego pueda compararlo con otra cosa.
  • Cobertura de código: Jest le permite obtener informes de cobertura de código de sus pruebas. Estos informes muestran qué porcentaje de su código se está probando actualmente e incluso puede ver las líneas exactas de código que no se están cubriendo actualmente.
  • Biblioteca de simulación: Jest también funciona como una biblioteca de simulación en el sentido de que le permite simular datos (como una función o un módulo) y usarlos en sus pruebas.

Algunas alternativas bien conocidas a Jest son Mocha , Jasmine y Karma .

Aquí hay un pequeño y agradable video que explica qué es Jest.

¿Qué es la biblioteca de pruebas?

La biblioteca de pruebas no es un ejecutor de pruebas, sino un conjunto de utilidades que funcionarán junto con un ejecutor de pruebas como Jest o Mocha. Estas utilidades son herramientas que podemos usar para probar nuestro código fácilmente y con un enfoque más profundo en la experiencia del usuario (pruebas de caja negra).

La biblioteca de prueba fue desarrollada por Kent C Dodds (quien también es uno de los mejores maestros de JS en la tierra, por lo que le recomiendo que lo siga).

Citando los documentos oficiales:

"La familia de bibliotecas Testing Library es una solución muy liviana para realizar pruebas sin todos los detalles de implementación.

Las principales utilidades que proporciona implican la consulta de nodos de forma similar a cómo los usuarios los encontrarían. De esta manera, testing-library ayuda a garantizar que sus pruebas darle confianza en su código de interfaz de usuario".

En lenguaje sencillo, con la biblioteca de prueba podemos probar elementos de la interfaz de usuario (como un párrafo, un botón, un div...) en lugar de probar el código responsable de representar la interfaz de usuario.

El principio detrás de la biblioteca es:

"Cuanto más se parezcan sus pruebas a la forma en que se usa su software, más confianza pueden brindarle".

... y eso es exactamente lo que queremos decir con pruebas de "caja negra". 😉

La biblioteca de prueba es en realidad un conjunto de bibliotecas , cada una creada para lograr el mismo objetivo pero adaptada para trabajar con diferentes tecnologías como React, Angular, Vue, Svelte, React Native y más... Es por eso que puede escuchar "React-testing". -biblioteca" o "Vue-testing-library". Es lo mismo pero adaptado para trabajar con diferentes tecnologías.

React-testing-library se instala de forma predeterminada al configurar aplicaciones React con create-react-app .

Una alternativa a la biblioteca de prueba es Enzyme (un conjunto de utilidades de prueba de interfaz de usuario desarrollado por Airbnb).

¿Qué es el ciprés?

Cypress es un corredor de pruebas de código abierto que le permite ejecutar sus proyectos en un navegador automatizado, de la misma manera que lo haría un usuario.

Con Cypress, podemos programar lo que hará el navegador (como visitar una URL, hacer clic en un botón, completar y enviar un formulario...) y verificar que cada acción coincida con la respuesta correspondiente.

Lo bueno de esto es que las pruebas se parecen MUCHO a lo que experimentará el usuario. Y dado que el objetivo principal de hacer software es el usuario, cuanto más cerca estemos de su perspectiva, más cerca deberíamos estar de detectar los errores más significativos en nuestro código. (Además, es genial ver que un navegador automático revisa toda la aplicación en solo unos segundos... 🤓)

Otra buena característica de Cypress es el "viaje en el tiempo". En el navegador automatizado de Cypress podemos ver todas las pruebas que hemos escrito y simplemente pasar el cursor sobre ellas para ver una instantánea gráfica de su resultado. Es algo muy útil para comprender mejor qué es lo que se rompe y cuándo.

Aunque se puede usar para pruebas unitarias y de integración, Cypress se usa principalmente para pruebas de extremo a extremo, ya que puede evaluar fácilmente funciones completas en cuestión de segundos.

Puede usar Cypress para probar cualquier cosa que se ejecute en un navegador, por lo que puede implementarlo fácilmente en React, Angular, Vue, etc.

A diferencia de Jest y React-Testing-Library, Cypress no viene preinstalado con la aplicación create-react-app. Pero podemos instalarlo fácilmente con NPM o el administrador de paquetes de su elección.

Algunas alternativas a Cypress son Selenium y Puppeteer .

Aquí hay un dulce video de Fireship que explica qué es Cypress y cómo funciona.

Comentario adicional: ...y cada vez que hablo de Cypress esto me viene a la mente . 😎

¿Qué es Supertest?

Supertest es una biblioteca que simula solicitudes HTTP. Es muy útil probar aplicaciones de nodo de back-end junto con Jest (como veremos en los siguientes ejemplos).

Resumen de herramientas

Como resumen rápido sobre este tema:

  • Jest es la biblioteca que usaremos para escribir y ejecutar pruebas para JavaScript.
  • La biblioteca de prueba funciona junto con Jest y nos proporciona funciones y métodos para probar la interfaz de usuario directamente, olvidándonos del código que hay detrás.
  • Cypress ejecuta su aplicación en un navegador simulado y verifica si las acciones realizadas en la interfaz de usuario responden como se esperaba.
  • Supertest es una biblioteca que simula solicitudes HTTP y se puede usar junto con Jest para probar aplicaciones de back-end.

Ahora comencemos con la parte divertida...

giphy-2

¡¡Que empiecen las pruebas!!

Cómo probar el código Vanilla JS

Ok, comencemos probando un código JS simple de vainilla. La idea aquí es ver cómo podemos implementar Jest en nuestro proyecto y aprender los conceptos básicos de cómo funciona.

Comencemos creando un nuevo directorio en nuestra máquina y creando una aplicación Node con npm init -y. Luego instale Jest ejecutándolo npm i -D jest( -Dlo guarda como una dependencia de desarrollo).

Ahora debería ver algo como esto en su package.jsonarchivo: "devDependencies": { "jest": "^27.5.1" } .

Y hablando de eso, en su package.json, reemplace su testscript con "test": "jest". Esto nos permitirá ejecutar nuestras pruebas más adelante ejecutando npm test. ;)

Su archivo completo package.jsondebería verse así:

{
  "name": "vanillatesting",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "jest"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "jest": "^27.5.1"
  }
}

¡Genial, estamos listos para escribir algo de JS que podamos probar! Cree un index.jsarchivo y coloque este código en él:

// index.js
function isPalindrome(string) {
    // O(n)
    // Put a pointer at each extreme of the word and iterate "inwards"
    // At each iteration, check if the pointers represent equal values
    // If this condition isn't accomplished, the word isn't a palindrome
    let left = 0
    let right = string.length-1
  
    while (left < right) {
        if (string[left] === string[right]) {
            left += 1
            right -= 1
        }
        else return false
    }
  
    return true
}

Esta función es un comprobador palíndromo . Recibe una cadena como parámetro y devuelve truesi la cadena es un palíndromo y falsesi no lo es. (Esta es una pregunta clásica de una entrevista técnica por cierto, pero eso es materia para otro artículo.🤫)

Vea que estamos exportando la función también. Comentario adicional: si desea saber más sobre esto y cómo funcionan los módulos JS, recientemente escribí un artículo al respecto.

Genial, ahora probemos esta función y veamos si funciona como se esperaba. Vamos a crear un archivo llamado index.test.js.

Este archivo es donde escribiremos nuestras pruebas. El sufijo que estamos usando ( .test.js) es importante aquí, ya que Jest identificará automáticamente los .testarchivos y los ejecutará cuando le ordenemos a Jest que pruebe nuestro proyecto.

Jest también identifica archivos con el .specsufijo like index.spec.js(para "especificación", que se refiere a los requisitos de su proyecto). Personalmente prefiero .testya que me parece más explícito, pero ambos funcionan igual.

¡Ahora escribamos nuestras primeras pruebas! Pon esto dentro de tu index.test.jsarchivo.

// index.test.js
isPalindrome = require('./index.js')

test('neuquen is palindrom', () => {
    expect(isPalindrome("neuquen")).toBe(true)
})

test('bariloche is not palindrom', () => {
    expect(isPalindrome("bariloche")).toBe(false)
})

Recapitulemos lo que realmente estamos haciendo:

  1. Requerir la función que queremos probar:isPalindrome = require('./index.js')
  2. La test()función la proporciona Jest y dentro de ella pondremos el código que queremos que Jest ejecute.
  3. test()toma dos parámetros. El primero es una descripción de la prueba, que es un nombre distintivo que se mostrará en nuestra consola cuando se ejecute la prueba. Veremos un ejemplo en un segundo.
  4. El segundo parámetro es una devolución de llamada, que contiene el código de prueba real.
  5. Dentro de esta devolución de llamada estamos llamando a la expect()función (también proporcionada por Jest). expect()toma nuestra función como parámetro, que a su vez recibe un parámetro que inventamos.
  6. Por último, encadenamos la .toBe()función (proporcionada también por Jest) y como parámetro le pasamos el valor que esperamos isPalindrome()devolver para cada caso. ("neuquen" es un palíndromo, por lo que nuestra función debería devolver true, y "bariloche" no lo es, por lo que debería devolver false).

Una de las cosas que más me gustan de Jest es lo fácil que es configurarlo. Otra cosa que me gusta mucho es lo autoexplicativa que es su sintaxis. Tenga en cuenta que podemos entender fácilmente lo que evaluarán nuestras pruebas con solo leerlas.👌

¡Ahora intentemos esto! Si ejecutamos npm testen nuestra consola, deberíamos obtener lo siguiente:

// console
> jest PASS 
./index.test.js
✓ neuquen is palindrom (1 ms)
✓ bariloche is not palindrom

Test Suites: 1 passed, 1
total Tests:       2 passed, 2
total Snapshots:   0
total Time:        0.244 s
Ran all test suites.

Felicidades, acabas de pasar tu primera prueba de Jest.

mr-miyagi-nod-1

vamos-a-empezar-esta-fiesta-sí-1

Para ver cómo se ve también una prueba fallida, cambiemos nuestra función editando las returnlíneas.

// index.js
function isPalindrome(string) {
    // O(n)
    // Put a pointr at each extreme of the word and iterate "inwards"
    // At each iteration, check if the pointers represent equal values
    // If this condition isn't accomplished, the word isn't a palindrome
    let left = 0
    let right = string.length-1
  
    while (left < right) {
        if (string[left] === string[right]) {
            left += 1
            right -= 1
        }
        else return 1
    }
  
    return 2
}

Ahora deberías obtener algo como esto:

// console
> vanillatesting@1.0.0 test
> jest

 FAIL  ./index.test.js
  ✕ neuquen is palindrom (4 ms)
  ✕ bariloche is not palindrom

  ● neuquen is palindrom

    expect(received).toBe(expected) // Object.is equality

    Expected: true
    Received: 2

      3 | // describe('isPalindrome function', () => {
      4 |   test('neuquen is palindrom', () => {
    > 5 |     expect(isPalindrome("neuquen")).toBe(true)
        |                                     ^
      6 |   })
      7 |
      8 |   test('bariloche is not palindrom', () => {

      at Object.<anonymous> (index.test.js:5:37)

  ● bariloche is not palindrom

    expect(received).toBe(expected) // Object.is equality

    Expected: false
    Received: 1

       7 |
       8 |   test('bariloche is not palindrom', () => {
    >  9 |     expect(isPalindrome("bariloche")).toBe(false)
         |                                       ^
      10 |   })
      11 | // })
      12 |

      at Object.<anonymous> (index.test.js:9:39)

Test Suites: 1 failed, 1 total
Tests:       2 failed, 2 total
Snapshots:   0 total
Time:        0.28 s, estimated 1 s
Ran all test suites.

Vea que obtenga una buena descripción de qué pruebas fallaron y en qué punto fallaron. En nuestro caso, fallaron cuando afirmamos (verificamos) los valores devueltos.

Esto es muy útil y siempre debemos prestar atención a estas descripciones, ya que algunas veces nuestras pruebas pueden fallar porque no están escritas correctamente. Y normalmente no escribimos pruebas para nuestras pruebas, todavía... 😅 Entonces, cuando vea una prueba fallida, primero verifique que funcione como se esperaba y luego revise su código real.

Ahora agreguemos y probemos otra función para mostrar algunas características más de Jest:

// index.js
function twoSum(nums, target) {
    // O(n)
    // Iterate the array once
    // At each iteration, calculate the value needed to get to the target, which is target - currentValue
    // If the neededValue exists in the array, return [currentValue, neededValue], else continue iteration
	for (let i = 0; i < nums.length; i++) {
		const neededNum = target - nums[i]
		if (nums.indexOf(neededNum) !== -1 && nums.indexOf(neededNum) !== i) return [nums[i], nums[nums.indexOf(neededNum)]]
	}
    return false
}

module.exports = { isPalindrome, twoSum }

Esta es otra pregunta clásica de la entrevista. La función toma dos parámetros, una matriz de números y un número de valor objetivo. Lo que hace es identificar si hay dos números en la matriz que suman el valor del segundo parámetro. Si los dos valores existen en la matriz, los devuelve en una matriz y, si no, devuelve falso.

Ahora escribamos algunas pruebas para esto:

({ isPalindrome, twoSum } = require('./index.js'))

...

test('[2,7,11,15] and 9 returns [2, 7]', () => {
    expect(twoSum([2,7,11,15], 9)).toEqual([2,7])
})

test('[3,2,4] and 6 returns [2, 4]', () => {
    expect(twoSum([3,2,4], 6)).toEqual([2,4])
})

test('[3,2,4] and 10 returns false', () => {
    expect(twoSum([3,2,4], 10)).toBe(false)
})

Vea que la estructura es casi la misma, excepto que estamos usando un comparador diferente en dos de las pruebas, toEqual().

Los Matchers son las funciones que nos proporciona Jests para evaluar valores. Hay muchos tipos de emparejadores que se pueden usar para muchas ocasiones diferentes.

Por ejemplo, .toBe()se usa para evaluar primitivas como cadenas, números o booleanos. toEqual()se usa para evaluar objetos (que cubre casi todo lo demás en Javascript).

Si necesita comparar el valor devuelto con un número que podría usar , .toBeGreaterThan()etc.toBeGreaterThanOrEqual()

Para ver una lista completa de los comparadores disponibles, consulte los documentos .

Si ejecutamos nuestras pruebas ahora, obtendremos lo siguiente:

> vanillatesting@1.0.0 test
> jest

 PASS  ./index.test.js
  ✓ neuquen is palindrom (2 ms)
  ✓ bariloche is not palindrom
  ✓ [2,7,11,15] and 9 returns [2, 7] (1 ms)
  ✓ [3,2,4] and 6 returns [2, 4]
  ✓ [3,2,4] and 10 returns false (1 ms)

Test Suites: 1 passed, 1 total
Tests:       5 passed, 5 total
Snapshots:   0 total
Time:        0.256 s, estimated 1 s
Ran all test suites.

Eso está bien, pero los resultados de nuestras pruebas parecen un poco confusos. Y a medida que crece nuestro conjunto de pruebas, probablemente será más difícil identificar cada resultado por separado.

Para ayudarnos con esto, Jest nos proporciona una describe()función que podemos usar para agrupar las pruebas y mostrar los resultados de una manera más esquemática. Podemos usarlo así:

({ isPalindrome, twoSum } = require('./index.js'))

describe('isPalindrome function', () => {
  test('neuquen is palindrom', () => {
    expect(isPalindrome("neuquen")).toBe(true)
  })

  test('bariloche is not palindrom', () => {
    expect(isPalindrome("bariloche")).toBe(false)
  })
})

describe('twoSum function', () => {
  test('[2,7,11,15] and 9 returns [2, 7]', () => {
    expect(twoSum([2,7,11,15], 9)).toEqual([2,7])
  })

  test('[3,2,4] and 6 returns [2, 4]', () => {
    expect(twoSum([3,2,4], 6)).toEqual([2,4])
  })

  test('[3,2,4] and 10 returns false', () => {
    expect(twoSum([3,2,4], 10)).toBe(false)
  })
})

El primer parámetro es la descripción que queremos mostrar para el grupo de pruebas dado, y el segundo es una devolución de llamada que contiene nuestras pruebas. Ahora, si corremos de npm testnuevo, obtenemos esto 😎:

// console
> vanillatesting@1.0.0 test
> jest

 PASS  ./index.test.js
  isPalindrome function
    ✓ neuquen is palindrom (2 ms)
    ✓ bariloche is not palindrom
  twoSum function
    ✓ [2,7,11,15] and 9 returns [2, 7] (1 ms)
    ✓ [3,2,4] and 6 returns [2, 4]
    ✓ [3,2,4] and 10 returns false

Test Suites: 1 passed, 1 total
Tests:       5 passed, 5 total
Snapshots:   0 total
Time:        0.216 s, estimated 1 s
Ran all test suites.

Cómo probar una aplicación de front-end React con Jest y React Testing Library

Ahora que conocemos los conceptos básicos de Jest, veamos cómo podemos combinarlo con la biblioteca Testing para probar una aplicación React.

Para esto vamos a usar un ejemplo muy simple. Solo una página con texto aleatorio, un botón que alterna otro fragmento de texto, una entrada de texto y un botón que alterna la representación de la entrada.

Grabación-2022-04-23-a-21.11.24

Tenga en cuenta que usaremos create-react-app para crear esta aplicación (que tiene la biblioteca Jest y Testing instalada de forma predeterminada). Si no está utilizando create-react-app, es posible que deba instalar ambas bibliotecas y agregar alguna configuración adicional.

No vamos a ver ningún código React aquí, solo nos vamos a centrar en las pruebas.

La estructura de carpetas de nuestro proyecto es la siguiente:

> src
    > components
        - About.jsx
    - App.jsx
    - Index.js
    - setupTests.js

El setupTests.jsarchivo es importante aquí. Se crea de forma predeterminada con create-react-app con este contenido:

// jest-dom adds custom jest matchers for asserting on DOM nodes.
// allows you to do things like:
// expect(element).toHaveTextContent(/react/i)
// learn more: https://github.com/testing-library/jest-dom
import '@testing-library/jest-dom';

Importa globalmente la jest-dombiblioteca proporcionada por la biblioteca Testing, lo que nos brinda comparadores Jest adicionales que podemos usar para probar el DOM (como toHaveTextContent(), toBeInTheDocument(), etc.).

Veremos ejemplos en un momento, pero sepa que algunas de las funciones y comparadores que usaremos provienen de aquí.

Con respecto a nuestros archivos de prueba, la práctica común es tener un archivo de prueba diferente para cada componente que estamos probando.

En cuanto a dónde colocarlos, dos prácticas comunes son tenerlos todos juntos en una sola carpeta, similar __tests__o similar, o tener cada archivo de prueba en la misma carpeta que el componente que está probando.

Prefiero el último, ya que a menudo paso del código del componente al código de prueba, y es bueno tenerlos cerca. Pero en verdad no importa. Siempre que usemos los sufijos .testo .spec, Jest identificará y ejecutará los archivos de todos modos.

Habiendo creado nuestros archivos de prueba, nuestra estructura de carpetas debería verse así:

> src
    > components
        - About.jsx
        - About.test.jsx
    - App.jsx
    - Index.js
    - setupTests.js

¡Frio! Comencemos probando nuestro Aboutcomponente.

Primero, probemos que se está procesando correctamente, así:

// About.test.jsx
import { render, screen } from '@testing-library/react'
import About from './About'

describe('About', () => {

  test('About renders correctly', () => {
    render( <About/> )
    expect(screen.getByText("I'm the about page!")).toBeInTheDocument()
  })

})
  • Vea que empezamos importando dos cosas de la biblioteca Testing: import { render, screen } from '@testing-library/react'.

La renderfunción toma un componente React como parámetro y lo renderizará para que podamos probarlo.

screenes un objeto que viene con muchas consultas que podemos usar para probar la interfaz de usuario directamente, omitiendo los detalles de implementación y enfocándonos en lo que el usuario realmente verá.

  • Luego importamos nuestro Aboutcomponente:import About from './About'
  • Usamos las funciones describey testJest mencionadas anteriormente.
  • Renderizamos el Aboutcomponente:render( <About/> )
  • Usamos la expectfunción Jest, y como parámetro usamos el screenobjeto proporcionado por la biblioteca Testing. Usamos su getByTextconsulta, que escanea el componente React en busca del texto que pasamos como parámetro.
  • Para finalizar, usamos el comparador de la biblioteca .toBeInTheDocument()Testing, que solo verifica si se está procesando el resultado de la consulta anterior.

Luego podemos probar que el botón de alternar "Cambiar estado" funciona correctamente, así:

// About.test.jsx
import { render, screen } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import About from './About'

describe('About', () => {

  ...

  test('Switch state works correctly', async () => {
    render( <About/> )

    expect(screen.getByText("It's on!")).toBeInTheDocument()
    userEvent.click(screen.getByText('Switch state'))
    expect(screen.getByText("It's rolling!")).toBeInTheDocument()
    userEvent.click(screen.getByText('Switch state'))
    expect(screen.getByText("It's on!")).toBeInTheDocument()
  })

})

Vea que importamos una utilidad adicional llamada userEvent. Este es un objeto que contiene muchos métodos que podemos usar para simular eventos activados por el usuario, como clics, desplazamientos, escritura en una entrada, etc.

  • Primero verificamos que se represente la cadena predeterminada:expect(screen.getByText("It's on!")).toBeInTheDocument()
  • Luego simulamos un clic y comprobamos que la cadena cambia en la pantalla:
userEvent.click(screen.getByText('Switch state'))
expect(screen.getByText("It's rolling!")).toBeInTheDocument()
  • Y por último, simulamos otro clic y comprobamos que la cadena vuelve a su valor predeterminado:
userEvent.click(screen.getByText('Switch state'))
expect(screen.getByText("It's on!")).toBeInTheDocument()

Para terminar, vamos a escribir otra prueba para verificar que la entrada de texto y su alternancia funcionen correctamente.

import { render, screen } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import About from './About'

describe('About', () => {

  ...

  test('Input works correctly', async () => {
    render( <About/> )

    userEvent.type(screen.getByTestId("testInput"), "Testing the test")
    userEvent.click(screen.getByText("Print input"))

    expect(screen.getByText("Testing the test")).toBeInTheDocument()

    userEvent.click(screen.getByText("Print input"))
    expect(screen.queryByText("Testing the test")).not.toBeInTheDocument()
  })


})
  • Nuevamente usamos userEventpara simular el texto que se escribe en nuestro elemento de entrada:userEvent.type(screen.getByTestId("testInput"), "Testing the test")
  • Luego simulamos un clic en el botón de alternar y verificamos que el texto de entrada esté en el documento:
userEvent.click(screen.getByText("Print input"))
expect(screen.getByText("Testing the test")).toBeInTheDocument()
  • Y cerramos simulando otro clic y comprobando que la prueba ya no está presente:
userEvent.click(screen.getByText("Print input"))
expect(screen.getByText("Testing the test")).toBeInTheDocument()

Puede ver lo buenas que son las utilidades proporcionadas por las bibliotecas de prueba y lo fácil que es combinarlas con Jest. 🤓

Podemos ejecutar este archivo de prueba específico ejecutando npm test -- About.test.jsxy este es el resultado que obtenemos:

// console
PASS  src/components/About.test.jsx
  About
    ✓ About renders correctly (34 ms)
    ✓ Switch state works correctly (66 ms)
    ✓ Input works correctly (67 ms)

Test Suites: 1 passed, 1 total
Tests:       3 passed, 3 total
Snapshots:   0 total
Time:        0.997 s, estimated 1 s
Ran all test suites matching /About.test.jsx/i.

La última característica de Jest que me gustaría mostrarles es la cobertura de prueba .
Puede obtener un informe de cobertura ejecutando npm test -- --coverage.

Esto ejecutará sus pruebas normalmente y al final del informe de resultados debería ver algo como esto:

// console
...

----------------|---------|----------|---------|---------|-------------------
File            | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s 
----------------|---------|----------|---------|---------|-------------------
All files       |      75 |      100 |   85.71 |      70 |                   
 src            |       0 |      100 |       0 |       0 |                   
  App.jsx       |       0 |      100 |       0 |       0 | 7                 
  App.t.js      |       0 |        0 |       0 |       0 |                   
  index.js      |       0 |      100 |     100 |       0 | 5-6               
 src/components |     100 |      100 |     100 |     100 |                   
  About.jsx     |     100 |      100 |     100 |     100 |                   
----------------|---------|----------|---------|---------|-------------------

En el informe podemos ver que nuestro About.jsxcomponente está completamente cubierto, pero nuestros archivos App.jsxy no están siendo probados.index.js

Esta función es muy útil cuando se trabaja en grandes proyectos y desea saber rápidamente si la mayor parte de su código se está probando correctamente.

Cómo probar una aplicación React de front-end con Cypress

Hemos hablado mucho sobre Jest, así que ahora echemos un vistazo a cómo podemos probar nuestra aplicación usando Cypress.

Comenzaremos instalando Cypress ejecutando npm i -D cypress.

Esto debería agregar esto a nuestro package.json:

"devDependencies": {
    "cypress": "^9.5.4"
}

Entonces correremos npx cypress open. Esto abrirá el navegador Cypress y creará un cypressdirectorio dentro de nuestro proyecto. Dentro de este directorio encontrará ejemplos, documentación y opciones de configuración.

También encontrarás una carpeta de "integración", en la que tenemos que poner nuestras pruebas. Entonces, creemos nuestro About.test.jsarchivo en esa carpeta y repliquemos los mismos ejemplos de prueba que hemos visto con Jest:

// About.test.js
describe('AboutPage', () => {
    it('Renders correctly', () => {
        cy.visit('http://localhost:3000/about')
        cy.contains("I'm the about page!")
    })

    it('switch btn toggles text', () => {
        cy.contains("It's on!")
        cy.get('.switchBtn').click()
        cy.contains("It's rolling!")
        cy.get('.switchBtn').click()
        cy.contains("It's on!")
    })

    it('Input works correctly', () => {
        cy.get(".testInput").type("Testing the test")
        cy.get('.printInputBtn').click()
        cy.contains("Testing the test")

        cy.get('.printInputBtn').click()
        cy.contains("Testing the test").should('not.exist')
    })
})
  • La describefunción funciona igual que en broma.
  • it()es la misma que la test()función que hemos visto anteriormente.
  • En la primera prueba, le decimos al navegador que visite la URL de nuestra aplicación y verifique que se muestre el texto correspondiente:
cy.visit('http://localhost:3000/about')
cy.contains("I'm the about page!")
  • Luego verificamos que se represente el texto de alternancia predeterminado, simulamos un clic y verificamos que cambia en consecuencia:
cy.contains("It's on!")
cy.get('.switchBtn').click()
cy.contains("It's rolling!")
cy.get('.switchBtn').click()
cy.contains("It's on!")
  • Y para terminar, simulamos una entrada de texto, simulamos un clic y verificamos que el texto de entrada se represente:
cy.get(".testInput").type("Testing the test")
cy.get('.printInputBtn').click()
cy.contains("Testing the test")

cy.get('.printInputBtn').click()
cy.contains("Testing the test").should('not.exist')

La sintaxis es ligeramente diferente a Jest, pero la idea y la estructura son más o menos las mismas.🤙

Ahora si volvemos a ejecutar npx cypress open, debería abrirse una ventana con este contenido:
2022-04-23_22-30

Podemos hacer clic en "Ejecutar especificación de integración" y nuestra prueba se ejecutará automáticamente en el navegador simulado. Después de que se hayan ejecutado las pruebas, en el panel izquierdo veremos los resultados:
2022-04-23_22-31

Podemos abrir esos resultados para ver cada paso que ejecutó la prueba. Si pasamos el cursor sobre cada paso, lo veremos ejecutado en el navegador en tiempo real. Una característica realmente dulce de Cypress.👌👌

2022-04-23_22-34

Como puede ver, es muy fácil configurar pruebas con Cypress. Y si ya está familiarizado con Jest, puede retomarlo rápidamente ya que la sintaxis no es tan diferente.

Si se pregunta si tiene sentido usar Jest y Cypress como corredores de prueba en el mismo proyecto, creo que esta respuesta de desbordamiento de pila lo resume bastante bien.

Cómo probar una aplicación de nodo back-end

Ahora que tenemos una comprensión básica de las formas en que podemos probar una aplicación de front-end, crucemos el río y veamos cómo podemos usar herramientas similares para probar una aplicación de back-end.

Para esto, usaremos un Nodo simple y una API Express con solo 3 puntos finales.

Cree un directorio y ejecútelo npm init -ypara crear una aplicación Node. Ejecute npm i expresspara instalar Express y luego ejecute npm i -D jest supertestpara instalar tanto Jest como Supertest como dependencias de desarrollo.

Dentro de su package.json, agregue "scripts": { "test": "jest" }.
Su totalidad package.jsondebe verse así:

{
  "dependencies": {
    "express": "^4.17.3"
  },
  "devDependencies": {
    "jest": "^27.5.1",
    "supertest": "^6.2.2"
  },
    "scripts": {
    "test": "jest"
  }
}

Luego crea un app.jsarchivo y pon este código en él:

// app.js
/* Import and initialize express */
const express = require('express')
const app = express()
const server = require('http').Server(app)
/* Global middlewares */
app.use(express.json())

/* Endpoint 1 */
app.get('/', async (req, res) => {

    try {
        res.status(200).json({ greeting: "Hello there!" })
    } catch (err) {
        res.status(500).send(err)
    }
})

/* Endpoint 2 */
app.get('/isPalindrome', async (req, res) => {

    try {
        const string = req.body.string
        let result = true        
        let left = 0
        let right = string.length-1
        
        while (left < right && result) {
            if (string[left] === string[right]) {
                left += 1
                right -= 1
            }
            else result = false
        }
        
        res.status(200).json({ result: result })
        
    } catch (err) {
        res.status(500).send(err)
    }
})

/* Endpoint 3 */
app.get('/twoSum', async (req, res) => {
    
    try {
        const nums = JSON.parse(req.body.nums)
        const target = JSON.parse(req.body.target)

        let result = false
        
        for (let i = 0; i < nums.length; i++) {
            const neededNum = target - nums[i]
            if (nums.indexOf(neededNum) !== -1 && nums.indexOf(neededNum) !== i) result = [nums[i], nums[nums.indexOf(neededNum)]]
        }
        
        res.status(200).json({ result: result })
        
    } catch (err) {
        res.status(500).send(err)
    }
})

/* Export server object */
module.exports = server

/* Initialize server */
server.listen(3001, () => console.log('Server is listening.') )
server.on('error', error => console.error(error) )

Como puede ver, el extremo 1 solo devuelve un mensaje de saludo. Los extremos 2 y 3 son adaptaciones de las funciones que hemos visto en nuestros ejemplos de JS estándar. Ahora reciben los parámetros dentro de la solicitud y los valores de retorno van en la respuesta. 😉

¡Ahora las pruebas! Cree un app.test.jsarchivo y coloque este código dentro de él:

// app.test.js
const supertest = require('supertest') // Import supertest
const server = require("./app") // Import the server object
const requestWithSupertest = supertest(server) // We will use this function to mock HTTP requests

afterEach(done => { // afterEach function is provided by Jest and executes once all tests are finished
    server.close() // We close the server connection once all tests have finished
    done()
})

test('GET "/" returns greeting', async () => {
    const res = await requestWithSupertest.get('/')
    expect(res.status).toEqual(200)
    expect(res.type).toEqual(expect.stringContaining('json'))
    expect(res.body).toEqual({ greeting: "Hello there!" })
})

describe("/isPalindrome", () => {
    test('GET "/isPalindrome" neuquen returns true', async () => {
        const res = await requestWithSupertest.get('/isPalindrome').set('Content-type', 'application/json').send({ "string":"neuquen" })
        expect(res.status).toEqual(200)
        expect(res.type).toEqual(expect.stringContaining('json'))
        expect(res.body).toEqual({ result: true })
    })

    test('GET "/isPalindrome" bariloche returns true', async () => {
        const res = await requestWithSupertest.get('/isPalindrome').set('Content-type', 'application/json').send({ "string":"bariloche" })
        expect(res.status).toEqual(200)
        expect(res.type).toEqual(expect.stringContaining('json'))
        expect(res.body).toEqual({ result: false })
    })
})

describe("/twoSum", () => {
    test('GET "/twoSum" [2,7,11,15] and 9 returns [7, 2]', async () => {
        const res = await requestWithSupertest.get('/twoSum').set('Content-type', 'application/json').send({ "nums":"[2,7,11,15]", "target": "9" })
        expect(res.status).toEqual(200)
        expect(res.type).toEqual(expect.stringContaining('json'))
        expect(res.body).toEqual({ result: [7, 2] })
    })

    test('GET "/twoSum" [3,2,4] and 6 returns [4, 2]', async () => {
        const res = await requestWithSupertest.get('/twoSum').set('Content-type', 'application/json').send({ "nums":"[3,2,4]", "target": "6" })
        expect(res.status).toEqual(200)
        expect(res.type).toEqual(expect.stringContaining('json'))
        expect(res.body).toEqual({ result: [4, 2] })
    })

    test('GET "/twoSum" [3,2,4] and 10 returns false', async () => {
        const res = await requestWithSupertest.get('/twoSum').set('Content-type', 'application/json').send({ "nums":"[3,2,4]", "target": "10" })
        expect(res.status).toEqual(200)
        expect(res.type).toEqual(expect.stringContaining('json'))
        expect(res.body).toEqual({ result: false })
    })
})

Analicemos lo que estamos haciendo:

  • Nos burlamos de la solicitud conrequestWithSupertest.get('/')
  • Luego "rompemos" el resobjeto en pedazos y afirmamos cada parte del mismo:
    • Verifique el estado de la respuesta:expect(res.status).toEqual(200)
    • Revisa el formato de respuesta:expect(res.type).toEqual(expect.stringContaining('json'))
    • Compruebe el contenido del cuerpo de la respuesta:expect(res.body).toEqual({ greeting: "Hello there!" })

Las otras pruebas son muy similares, excepto que enviamos datos en los cuerpos de las solicitudes simuladas, así:

const res = await requestWithSupertest.get('/isPalindrome').set('Content-type', 'application/json').send({ "string":"bariloche" })

Como puede ver, probar de esta manera es realmente simple una vez que está familiarizado con Jest. Solo necesitamos un poco de ayuda de Supertest para simular la solicitud HTTP y el resto es solo afirmar la respuesta. 👏👏

Podemos ejecutar nuestras pruebas con npm testy deberíamos obtener la siguiente respuesta:

// console
 PASS  ./app.test.js
  ✓ GET "/" returns greeting (46 ms)
  /isPalindrome
    ✓ GET "/isPalindrome" neuquen returns true (18 ms)
    ✓ GET "/isPalindrome" bariloche returns true (3 ms)
  /twoSum
    ✓ GET "/twoSum" [2,7,11,15] and 9 returns [7, 2] (4 ms)
    ✓ GET "/twoSum" [3,2,4] and 6 returns [4, 2] (3 ms)
    ✓ GET "/twoSum" [3,2,4] and 10 returns false (2 ms)

Test Suites: 1 passed, 1 total
Tests:       6 passed, 6 total
Snapshots:   0 total
Time:        0.552 s, estimated 1 s
Ran all test suites.

Envolver

¡Y eso es! Hemos cubierto los conceptos básicos de cuatro herramientas muy populares que le permitirán probar tanto el front-end como el back-end de sus aplicaciones JS.

Por supuesto, hay mucho más en todas las herramientas que hemos visto y muchas características que no hemos cubierto. Pero la idea era darte una introducción para que puedas dar tus primeros pasos en el mundo del testing.

Como siempre, espero que hayas disfrutado el artículo y hayas aprendido algo nuevo. 

¡Salud y nos vemos en la próxima! =D

adiós--1- 

Fuente: https://www.freecodecamp.org/news/test-a-react-app-with-jest-testing-library-and-cypress/

#jest #testing #cypress #react 

What is GEEK

Buddha Community

Probar Sus Aplicaciones Usando Jest, Testing Library, Cypress Y Supert

Probar Sus Aplicaciones Usando Jest, Testing Library, Cypress Y Supert

¡Hola a todos! En este artículo vamos a hablar sobre las pruebas. Le daré una buena descripción general de lo que es la prueba y una introducción de cómo puede implementarla en sus proyectos de JavaScript. Usaremos cuatro herramientas muy populares: Jest, Testing library, Cypress y Supertest.

Primero, hablaremos sobre qué es la prueba, por qué es una buena idea probar nuestro código y los diferentes tipos de pruebas que se pueden implementar.

Luego, presentaremos cada una de las herramientas que usaremos y, finalmente, daremos ejemplos prácticos para el código JS estándar, una aplicación React de front-end y una aplicación Node de back-end.

¡Vámonos!

¿Qué es la prueba y por qué es valiosa?

La prueba es la práctica de verificar si una pieza de software funciona como se esperaba. Esto a menudo se reconoce como control de calidad o garantía de calidad, y tiene como objetivo reducir al mínimo la cantidad de errores que llegan a la producción.

Probamos el software para identificar errores, brechas o requisitos faltantes y arreglamos esas cosas antes de enviar el código a producción.

Probar nuestro código a fondo mejora la confiabilidad de nuestro proyecto, nos ahorra tiempo de corrección de errores posteriores y, por lo tanto, reduce los costos y mejora la posibilidad de que nuestro cliente esté completamente satisfecho con nuestro producto.

BvIJ1M5-1

Aquí hay un buen video corto de Fireship que presenta algunos de los conceptos de los que hablaremos más adelante.

Diferentes tipos de pruebas

Las prácticas de prueba se pueden clasificar en diferentes tipos de acuerdo con muchos factores. Personalmente, creo que hay mucho galimatías sobre este tema, con cientos de términos que a menudo se refieren a cosas muy similares. Así que hagámoslo simple y revisemos solo los términos más populares y lo que significan.

Esto ayudará a aclarar las muchas formas en que se puede probar un software y a comprender mejor las herramientas que presentaremos más adelante.

Pruebas manuales vs automatizadas

Dependiendo de las herramientas que utilicemos para probar nuestro software, podemos clasificar las pruebas en pruebas manuales o automatizadas .

La prueba manual es la práctica de "hacer clic" y verificar manualmente todas las funciones que tiene nuestro producto, simulando lo que haría un usuario real.

Las pruebas automatizadas se realizan a través de código, escribiendo programas que verifican cómo se ejecuta nuestra aplicación.

Hay muchos marcos de prueba y bibliotecas que podemos usar para esto. Cuando se trata de pruebas funcionales (vamos a ver lo que eso significa en un segundo), la mayoría de las bibliotecas funcionan de manera similar:

  • Primero definimos qué pieza de código queremos probar.
  • Luego le proporcionamos a ese fragmento de código algún tipo de entrada o ejecutamos una acción sobre él.
  • Luego definimos qué debe hacer esa pieza de código dada la entrada/acción que realizamos.
  • Y finalmente compararemos lo que ese fragmento de código realmente hizo con lo que dijimos que debería hacer.

Si hizo lo que dijimos que debería hacer, la prueba pasó. Si no lo hizo, fracasó.

Pruebas funcionales vs no funcionales

Las pruebas funcionales se refieren a las características reales de nuestro producto . Por ejemplo, si tenemos una plataforma de blogs, las pruebas funcionales deberían garantizar que los usuarios puedan crear nuevos artículos, editar esos artículos, navegar a través de artículos escritos por otras personas, etc.

Las pruebas no funcionales se refieren a cualquier cosa que no esté estrictamente relacionada con las características principales de nuestro producto. Y que nuevamente se puede clasificar en diferentes categorías, por ejemplo:

  • Las pruebas de estrés verifican cómo responde la infraestructura al uso intensivo.
  • Las pruebas de seguridad verifican si una aplicación es vulnerable a ataques de piratería comunes.
  • Las pruebas de accesibilidad verifican si una aplicación está codificada de manera que sea accesible para personas con diferentes discapacidades.

Pruebas unitarias frente a pruebas de integración frente a pruebas de extremo a extremo

Otra forma de clasificar las pruebas es según su amplitud o exhaustividad.

Las pruebas unitarias tienen como objetivo probar funciones individuales, métodos o pequeños fragmentos de código de forma independiente. En las pruebas unitarias, se comprueban pequeños fragmentos de código de forma aislada.

Las pruebas de integración verifican cómo las piezas individuales de código interactúan entre sí y funcionan juntas. En las pruebas de integración, juntamos piezas y vemos si interactúan correctamente.

Las pruebas de extremo a extremo , también conocidas como E2E, ejecutan programas en un entorno simulado que emula el comportamiento real del usuario. Teniendo un sitio web como ejemplo, nuestro código se abriría en un navegador real y todas las funciones se ejecutarían de la misma manera que las usaría un usuario. Las pruebas E2E se parecen mucho a las pruebas manuales en ese sentido, pero totalmente automatizadas.

La prueba E2E es el tipo más amplio o completo de estos tres, ya que evalúa características y comportamientos completos, no partes específicas de nuestro código.

Pruebas de caja blanca, caja negra y caja gris

La última clasificación que vamos a ver depende de cuánto se centren nuestras pruebas en los detalles de implementación o la experiencia del usuario.

Digamos que tenemos un sitio web simple con un botón que, cuando se hace clic, abre un modal. En nuestro código, el botón tiene un detector de eventos de clic que ejecuta una función. Esa función cambia la clase CSS de nuestro elemento HTML modal, y eso hace que el modal se represente en la pantalla.

Hablamos de pruebas de " caja blanca " cuando probamos los detalles de implementación . Siguiendo el ejemplo, bajo este paradigma podríamos probar que el clic del botón ejecuta la función correspondiente, y que después de la ejecución de la función, la clase CSS de nuestro elemento modal cambia en consecuencia.

Otra forma de hacer esto es olvidarse de la implementación y simplemente verificar si el modal se representa después de hacer clic en el botón. No nos importa cuál sea la clase CSS, o si la función correspondiente se ejecuta o no. Solo nos enfocamos en probar lo que el usuario debe percibir. Esa es la prueba de " caja negra ".

Y, como habrá adivinado, la prueba de "caja gris" es solo una combinación de las dos anteriores.

Una última cosa que mencionar aquí es que estos diferentes tipos de pruebas no son necesariamente excluyentes entre sí. Quiero decir, pueden y, a menudo, se implementan al mismo tiempo en los mismos proyectos.

Es muy común tener pruebas tanto manuales como automatizadas, pruebas funcionales y no funcionales, pruebas unitarias y E2E... La idea siempre será tratar de anticipar y resolver el mayor número posible de problemas en un tiempo y esfuerzo razonables.

Cuándo probar

Esto puede parecer una pregunta simple al principio, pero en realidad también hay diferentes enfoques para esto.

A algunas personas les gusta probar su aplicación una vez que se ha desarrollado por completo. A otros les gusta escribir pruebas al mismo tiempo que codifican la aplicación y prueban cada función a medida que se desarrolla.

A otros les gusta escribir primero las pruebas antes que nada, definiendo de esta manera los requisitos mínimos que debe cumplir el programa. Y luego codifican la aplicación de una manera que pasa esas pruebas lo más rápido posible (esto se llama desarrollo basado en pruebas o TDD ).

Una vez que haya desarrollado una aplicación o una función completa y tenga un conjunto de pruebas (un conjunto de pruebas es un grupo de pruebas que verifican una función particular o una aplicación completa), otra práctica común es ejecutar sus pruebas cada vez que haga cualquier tipo de modificación en el código base, para verificar que nada se rompa.

Por último, si tiene un sistema CI/CD , es común automatizar la ejecución de pruebas antes de cualquier implementación. De modo que si alguna prueba falla, la implementación se detiene y se envía algún tipo de alerta (lo que, por supuesto, siempre es mejor que ver que tu aplicación se incendia en la producción 🔥😱).

Al igual que con los tipos de prueba, es común probar las aplicaciones en diferentes momentos. Cada empresa normalmente tiene su propio programa de pruebas o práctica a seguir, adaptado a sus necesidades.

Nuestro conjunto de herramientas

Bien, ahora que tenemos una idea más clara de lo que son las pruebas y los tipos de pruebas que podemos realizar, revisemos las herramientas que usaremos en nuestros ejemplos.

Como se mencionó anteriormente, hay muchas bibliotecas diferentes para elegir para ejecutar nuestras pruebas. Elegí estos cuatro porque son algunos de los más populares cuando se trata de aplicaciones de JavaScript, pero sé que hay más opciones. Nombraré alternativas para la mayoría de las herramientas que usaremos en caso de que desee investigar más. 😉

que es broma

Jest es un corredor de pruebas de JavaScript. Un corredor de prueba es una pieza de software que le permite ejecutar pruebas para evaluar su aplicación. Es un proyecto de código abierto mantenido por Meta (anteriormente Facebook), y fue de código abierto por primera vez en 2014.

Comentario al margen: cada vez que digo "corredor de pruebas" me imagino esto. ¿Soy el único? 🤔

8gTI-1

¡Corredor de pruebas, no Blade Runner!

De todos modos... puedes usar Jest en proyectos que usan Babel , TypeScript , Node.js , React , Angular , Vue.js , Svelte y otras tecnologías también. Puede instalar Jest a través de NPM como cualquier biblioteca y requiere muy poca configuración para comenzar.

Jest viene instalado de forma predeterminada al configurar aplicaciones React con create-react-app .

Jest a menudo también se denomina marco de prueba, ya que viene con muchas otras características integradas además de solo ejecutar pruebas (que no es el caso con todos los ejecutores de prueba). Algunas de esas características son:

  • Biblioteca de afirmación: Jest viene con muchas funciones y métodos integrados que puede usar para afirmar su código (afirmar básicamente significa verificar si una parte del código se comporta como se espera).
  • Prueba de instantáneas: Jest le permite usar instantáneas, que son una forma de capturar un objeto grande y almacenarlo en la memoria para que luego pueda compararlo con otra cosa.
  • Cobertura de código: Jest le permite obtener informes de cobertura de código de sus pruebas. Estos informes muestran qué porcentaje de su código se está probando actualmente e incluso puede ver las líneas exactas de código que no se están cubriendo actualmente.
  • Biblioteca de simulación: Jest también funciona como una biblioteca de simulación en el sentido de que le permite simular datos (como una función o un módulo) y usarlos en sus pruebas.

Algunas alternativas bien conocidas a Jest son Mocha , Jasmine y Karma .

Aquí hay un pequeño y agradable video que explica qué es Jest.

¿Qué es la biblioteca de pruebas?

La biblioteca de pruebas no es un ejecutor de pruebas, sino un conjunto de utilidades que funcionarán junto con un ejecutor de pruebas como Jest o Mocha. Estas utilidades son herramientas que podemos usar para probar nuestro código fácilmente y con un enfoque más profundo en la experiencia del usuario (pruebas de caja negra).

La biblioteca de prueba fue desarrollada por Kent C Dodds (quien también es uno de los mejores maestros de JS en la tierra, por lo que le recomiendo que lo siga).

Citando los documentos oficiales:

"La familia de bibliotecas Testing Library es una solución muy liviana para realizar pruebas sin todos los detalles de implementación.

Las principales utilidades que proporciona implican la consulta de nodos de forma similar a cómo los usuarios los encontrarían. De esta manera, testing-library ayuda a garantizar que sus pruebas darle confianza en su código de interfaz de usuario".

En lenguaje sencillo, con la biblioteca de prueba podemos probar elementos de la interfaz de usuario (como un párrafo, un botón, un div...) en lugar de probar el código responsable de representar la interfaz de usuario.

El principio detrás de la biblioteca es:

"Cuanto más se parezcan sus pruebas a la forma en que se usa su software, más confianza pueden brindarle".

... y eso es exactamente lo que queremos decir con pruebas de "caja negra". 😉

La biblioteca de prueba es en realidad un conjunto de bibliotecas , cada una creada para lograr el mismo objetivo pero adaptada para trabajar con diferentes tecnologías como React, Angular, Vue, Svelte, React Native y más... Es por eso que puede escuchar "React-testing". -biblioteca" o "Vue-testing-library". Es lo mismo pero adaptado para trabajar con diferentes tecnologías.

React-testing-library se instala de forma predeterminada al configurar aplicaciones React con create-react-app .

Una alternativa a la biblioteca de prueba es Enzyme (un conjunto de utilidades de prueba de interfaz de usuario desarrollado por Airbnb).

¿Qué es el ciprés?

Cypress es un corredor de pruebas de código abierto que le permite ejecutar sus proyectos en un navegador automatizado, de la misma manera que lo haría un usuario.

Con Cypress, podemos programar lo que hará el navegador (como visitar una URL, hacer clic en un botón, completar y enviar un formulario...) y verificar que cada acción coincida con la respuesta correspondiente.

Lo bueno de esto es que las pruebas se parecen MUCHO a lo que experimentará el usuario. Y dado que el objetivo principal de hacer software es el usuario, cuanto más cerca estemos de su perspectiva, más cerca deberíamos estar de detectar los errores más significativos en nuestro código. (Además, es genial ver que un navegador automático revisa toda la aplicación en solo unos segundos... 🤓)

Otra buena característica de Cypress es el "viaje en el tiempo". En el navegador automatizado de Cypress podemos ver todas las pruebas que hemos escrito y simplemente pasar el cursor sobre ellas para ver una instantánea gráfica de su resultado. Es algo muy útil para comprender mejor qué es lo que se rompe y cuándo.

Aunque se puede usar para pruebas unitarias y de integración, Cypress se usa principalmente para pruebas de extremo a extremo, ya que puede evaluar fácilmente funciones completas en cuestión de segundos.

Puede usar Cypress para probar cualquier cosa que se ejecute en un navegador, por lo que puede implementarlo fácilmente en React, Angular, Vue, etc.

A diferencia de Jest y React-Testing-Library, Cypress no viene preinstalado con la aplicación create-react-app. Pero podemos instalarlo fácilmente con NPM o el administrador de paquetes de su elección.

Algunas alternativas a Cypress son Selenium y Puppeteer .

Aquí hay un dulce video de Fireship que explica qué es Cypress y cómo funciona.

Comentario adicional: ...y cada vez que hablo de Cypress esto me viene a la mente . 😎

¿Qué es Supertest?

Supertest es una biblioteca que simula solicitudes HTTP. Es muy útil probar aplicaciones de nodo de back-end junto con Jest (como veremos en los siguientes ejemplos).

Resumen de herramientas

Como resumen rápido sobre este tema:

  • Jest es la biblioteca que usaremos para escribir y ejecutar pruebas para JavaScript.
  • La biblioteca de prueba funciona junto con Jest y nos proporciona funciones y métodos para probar la interfaz de usuario directamente, olvidándonos del código que hay detrás.
  • Cypress ejecuta su aplicación en un navegador simulado y verifica si las acciones realizadas en la interfaz de usuario responden como se esperaba.
  • Supertest es una biblioteca que simula solicitudes HTTP y se puede usar junto con Jest para probar aplicaciones de back-end.

Ahora comencemos con la parte divertida...

giphy-2

¡¡Que empiecen las pruebas!!

Cómo probar el código Vanilla JS

Ok, comencemos probando un código JS simple de vainilla. La idea aquí es ver cómo podemos implementar Jest en nuestro proyecto y aprender los conceptos básicos de cómo funciona.

Comencemos creando un nuevo directorio en nuestra máquina y creando una aplicación Node con npm init -y. Luego instale Jest ejecutándolo npm i -D jest( -Dlo guarda como una dependencia de desarrollo).

Ahora debería ver algo como esto en su package.jsonarchivo: "devDependencies": { "jest": "^27.5.1" } .

Y hablando de eso, en su package.json, reemplace su testscript con "test": "jest". Esto nos permitirá ejecutar nuestras pruebas más adelante ejecutando npm test. ;)

Su archivo completo package.jsondebería verse así:

{
  "name": "vanillatesting",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "jest"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "jest": "^27.5.1"
  }
}

¡Genial, estamos listos para escribir algo de JS que podamos probar! Cree un index.jsarchivo y coloque este código en él:

// index.js
function isPalindrome(string) {
    // O(n)
    // Put a pointer at each extreme of the word and iterate "inwards"
    // At each iteration, check if the pointers represent equal values
    // If this condition isn't accomplished, the word isn't a palindrome
    let left = 0
    let right = string.length-1
  
    while (left < right) {
        if (string[left] === string[right]) {
            left += 1
            right -= 1
        }
        else return false
    }
  
    return true
}

Esta función es un comprobador palíndromo . Recibe una cadena como parámetro y devuelve truesi la cadena es un palíndromo y falsesi no lo es. (Esta es una pregunta clásica de una entrevista técnica por cierto, pero eso es materia para otro artículo.🤫)

Vea que estamos exportando la función también. Comentario adicional: si desea saber más sobre esto y cómo funcionan los módulos JS, recientemente escribí un artículo al respecto.

Genial, ahora probemos esta función y veamos si funciona como se esperaba. Vamos a crear un archivo llamado index.test.js.

Este archivo es donde escribiremos nuestras pruebas. El sufijo que estamos usando ( .test.js) es importante aquí, ya que Jest identificará automáticamente los .testarchivos y los ejecutará cuando le ordenemos a Jest que pruebe nuestro proyecto.

Jest también identifica archivos con el .specsufijo like index.spec.js(para "especificación", que se refiere a los requisitos de su proyecto). Personalmente prefiero .testya que me parece más explícito, pero ambos funcionan igual.

¡Ahora escribamos nuestras primeras pruebas! Pon esto dentro de tu index.test.jsarchivo.

// index.test.js
isPalindrome = require('./index.js')

test('neuquen is palindrom', () => {
    expect(isPalindrome("neuquen")).toBe(true)
})

test('bariloche is not palindrom', () => {
    expect(isPalindrome("bariloche")).toBe(false)
})

Recapitulemos lo que realmente estamos haciendo:

  1. Requerir la función que queremos probar:isPalindrome = require('./index.js')
  2. La test()función la proporciona Jest y dentro de ella pondremos el código que queremos que Jest ejecute.
  3. test()toma dos parámetros. El primero es una descripción de la prueba, que es un nombre distintivo que se mostrará en nuestra consola cuando se ejecute la prueba. Veremos un ejemplo en un segundo.
  4. El segundo parámetro es una devolución de llamada, que contiene el código de prueba real.
  5. Dentro de esta devolución de llamada estamos llamando a la expect()función (también proporcionada por Jest). expect()toma nuestra función como parámetro, que a su vez recibe un parámetro que inventamos.
  6. Por último, encadenamos la .toBe()función (proporcionada también por Jest) y como parámetro le pasamos el valor que esperamos isPalindrome()devolver para cada caso. ("neuquen" es un palíndromo, por lo que nuestra función debería devolver true, y "bariloche" no lo es, por lo que debería devolver false).

Una de las cosas que más me gustan de Jest es lo fácil que es configurarlo. Otra cosa que me gusta mucho es lo autoexplicativa que es su sintaxis. Tenga en cuenta que podemos entender fácilmente lo que evaluarán nuestras pruebas con solo leerlas.👌

¡Ahora intentemos esto! Si ejecutamos npm testen nuestra consola, deberíamos obtener lo siguiente:

// console
> jest PASS 
./index.test.js
✓ neuquen is palindrom (1 ms)
✓ bariloche is not palindrom

Test Suites: 1 passed, 1
total Tests:       2 passed, 2
total Snapshots:   0
total Time:        0.244 s
Ran all test suites.

Felicidades, acabas de pasar tu primera prueba de Jest.

mr-miyagi-nod-1

vamos-a-empezar-esta-fiesta-sí-1

Para ver cómo se ve también una prueba fallida, cambiemos nuestra función editando las returnlíneas.

// index.js
function isPalindrome(string) {
    // O(n)
    // Put a pointr at each extreme of the word and iterate "inwards"
    // At each iteration, check if the pointers represent equal values
    // If this condition isn't accomplished, the word isn't a palindrome
    let left = 0
    let right = string.length-1
  
    while (left < right) {
        if (string[left] === string[right]) {
            left += 1
            right -= 1
        }
        else return 1
    }
  
    return 2
}

Ahora deberías obtener algo como esto:

// console
> vanillatesting@1.0.0 test
> jest

 FAIL  ./index.test.js
  ✕ neuquen is palindrom (4 ms)
  ✕ bariloche is not palindrom

  ● neuquen is palindrom

    expect(received).toBe(expected) // Object.is equality

    Expected: true
    Received: 2

      3 | // describe('isPalindrome function', () => {
      4 |   test('neuquen is palindrom', () => {
    > 5 |     expect(isPalindrome("neuquen")).toBe(true)
        |                                     ^
      6 |   })
      7 |
      8 |   test('bariloche is not palindrom', () => {

      at Object.<anonymous> (index.test.js:5:37)

  ● bariloche is not palindrom

    expect(received).toBe(expected) // Object.is equality

    Expected: false
    Received: 1

       7 |
       8 |   test('bariloche is not palindrom', () => {
    >  9 |     expect(isPalindrome("bariloche")).toBe(false)
         |                                       ^
      10 |   })
      11 | // })
      12 |

      at Object.<anonymous> (index.test.js:9:39)

Test Suites: 1 failed, 1 total
Tests:       2 failed, 2 total
Snapshots:   0 total
Time:        0.28 s, estimated 1 s
Ran all test suites.

Vea que obtenga una buena descripción de qué pruebas fallaron y en qué punto fallaron. En nuestro caso, fallaron cuando afirmamos (verificamos) los valores devueltos.

Esto es muy útil y siempre debemos prestar atención a estas descripciones, ya que algunas veces nuestras pruebas pueden fallar porque no están escritas correctamente. Y normalmente no escribimos pruebas para nuestras pruebas, todavía... 😅 Entonces, cuando vea una prueba fallida, primero verifique que funcione como se esperaba y luego revise su código real.

Ahora agreguemos y probemos otra función para mostrar algunas características más de Jest:

// index.js
function twoSum(nums, target) {
    // O(n)
    // Iterate the array once
    // At each iteration, calculate the value needed to get to the target, which is target - currentValue
    // If the neededValue exists in the array, return [currentValue, neededValue], else continue iteration
	for (let i = 0; i < nums.length; i++) {
		const neededNum = target - nums[i]
		if (nums.indexOf(neededNum) !== -1 && nums.indexOf(neededNum) !== i) return [nums[i], nums[nums.indexOf(neededNum)]]
	}
    return false
}

module.exports = { isPalindrome, twoSum }

Esta es otra pregunta clásica de la entrevista. La función toma dos parámetros, una matriz de números y un número de valor objetivo. Lo que hace es identificar si hay dos números en la matriz que suman el valor del segundo parámetro. Si los dos valores existen en la matriz, los devuelve en una matriz y, si no, devuelve falso.

Ahora escribamos algunas pruebas para esto:

({ isPalindrome, twoSum } = require('./index.js'))

...

test('[2,7,11,15] and 9 returns [2, 7]', () => {
    expect(twoSum([2,7,11,15], 9)).toEqual([2,7])
})

test('[3,2,4] and 6 returns [2, 4]', () => {
    expect(twoSum([3,2,4], 6)).toEqual([2,4])
})

test('[3,2,4] and 10 returns false', () => {
    expect(twoSum([3,2,4], 10)).toBe(false)
})

Vea que la estructura es casi la misma, excepto que estamos usando un comparador diferente en dos de las pruebas, toEqual().

Los Matchers son las funciones que nos proporciona Jests para evaluar valores. Hay muchos tipos de emparejadores que se pueden usar para muchas ocasiones diferentes.

Por ejemplo, .toBe()se usa para evaluar primitivas como cadenas, números o booleanos. toEqual()se usa para evaluar objetos (que cubre casi todo lo demás en Javascript).

Si necesita comparar el valor devuelto con un número que podría usar , .toBeGreaterThan()etc.toBeGreaterThanOrEqual()

Para ver una lista completa de los comparadores disponibles, consulte los documentos .

Si ejecutamos nuestras pruebas ahora, obtendremos lo siguiente:

> vanillatesting@1.0.0 test
> jest

 PASS  ./index.test.js
  ✓ neuquen is palindrom (2 ms)
  ✓ bariloche is not palindrom
  ✓ [2,7,11,15] and 9 returns [2, 7] (1 ms)
  ✓ [3,2,4] and 6 returns [2, 4]
  ✓ [3,2,4] and 10 returns false (1 ms)

Test Suites: 1 passed, 1 total
Tests:       5 passed, 5 total
Snapshots:   0 total
Time:        0.256 s, estimated 1 s
Ran all test suites.

Eso está bien, pero los resultados de nuestras pruebas parecen un poco confusos. Y a medida que crece nuestro conjunto de pruebas, probablemente será más difícil identificar cada resultado por separado.

Para ayudarnos con esto, Jest nos proporciona una describe()función que podemos usar para agrupar las pruebas y mostrar los resultados de una manera más esquemática. Podemos usarlo así:

({ isPalindrome, twoSum } = require('./index.js'))

describe('isPalindrome function', () => {
  test('neuquen is palindrom', () => {
    expect(isPalindrome("neuquen")).toBe(true)
  })

  test('bariloche is not palindrom', () => {
    expect(isPalindrome("bariloche")).toBe(false)
  })
})

describe('twoSum function', () => {
  test('[2,7,11,15] and 9 returns [2, 7]', () => {
    expect(twoSum([2,7,11,15], 9)).toEqual([2,7])
  })

  test('[3,2,4] and 6 returns [2, 4]', () => {
    expect(twoSum([3,2,4], 6)).toEqual([2,4])
  })

  test('[3,2,4] and 10 returns false', () => {
    expect(twoSum([3,2,4], 10)).toBe(false)
  })
})

El primer parámetro es la descripción que queremos mostrar para el grupo de pruebas dado, y el segundo es una devolución de llamada que contiene nuestras pruebas. Ahora, si corremos de npm testnuevo, obtenemos esto 😎:

// console
> vanillatesting@1.0.0 test
> jest

 PASS  ./index.test.js
  isPalindrome function
    ✓ neuquen is palindrom (2 ms)
    ✓ bariloche is not palindrom
  twoSum function
    ✓ [2,7,11,15] and 9 returns [2, 7] (1 ms)
    ✓ [3,2,4] and 6 returns [2, 4]
    ✓ [3,2,4] and 10 returns false

Test Suites: 1 passed, 1 total
Tests:       5 passed, 5 total
Snapshots:   0 total
Time:        0.216 s, estimated 1 s
Ran all test suites.

Cómo probar una aplicación de front-end React con Jest y React Testing Library

Ahora que conocemos los conceptos básicos de Jest, veamos cómo podemos combinarlo con la biblioteca Testing para probar una aplicación React.

Para esto vamos a usar un ejemplo muy simple. Solo una página con texto aleatorio, un botón que alterna otro fragmento de texto, una entrada de texto y un botón que alterna la representación de la entrada.

Grabación-2022-04-23-a-21.11.24

Tenga en cuenta que usaremos create-react-app para crear esta aplicación (que tiene la biblioteca Jest y Testing instalada de forma predeterminada). Si no está utilizando create-react-app, es posible que deba instalar ambas bibliotecas y agregar alguna configuración adicional.

No vamos a ver ningún código React aquí, solo nos vamos a centrar en las pruebas.

La estructura de carpetas de nuestro proyecto es la siguiente:

> src
    > components
        - About.jsx
    - App.jsx
    - Index.js
    - setupTests.js

El setupTests.jsarchivo es importante aquí. Se crea de forma predeterminada con create-react-app con este contenido:

// jest-dom adds custom jest matchers for asserting on DOM nodes.
// allows you to do things like:
// expect(element).toHaveTextContent(/react/i)
// learn more: https://github.com/testing-library/jest-dom
import '@testing-library/jest-dom';

Importa globalmente la jest-dombiblioteca proporcionada por la biblioteca Testing, lo que nos brinda comparadores Jest adicionales que podemos usar para probar el DOM (como toHaveTextContent(), toBeInTheDocument(), etc.).

Veremos ejemplos en un momento, pero sepa que algunas de las funciones y comparadores que usaremos provienen de aquí.

Con respecto a nuestros archivos de prueba, la práctica común es tener un archivo de prueba diferente para cada componente que estamos probando.

En cuanto a dónde colocarlos, dos prácticas comunes son tenerlos todos juntos en una sola carpeta, similar __tests__o similar, o tener cada archivo de prueba en la misma carpeta que el componente que está probando.

Prefiero el último, ya que a menudo paso del código del componente al código de prueba, y es bueno tenerlos cerca. Pero en verdad no importa. Siempre que usemos los sufijos .testo .spec, Jest identificará y ejecutará los archivos de todos modos.

Habiendo creado nuestros archivos de prueba, nuestra estructura de carpetas debería verse así:

> src
    > components
        - About.jsx
        - About.test.jsx
    - App.jsx
    - Index.js
    - setupTests.js

¡Frio! Comencemos probando nuestro Aboutcomponente.

Primero, probemos que se está procesando correctamente, así:

// About.test.jsx
import { render, screen } from '@testing-library/react'
import About from './About'

describe('About', () => {

  test('About renders correctly', () => {
    render( <About/> )
    expect(screen.getByText("I'm the about page!")).toBeInTheDocument()
  })

})
  • Vea que empezamos importando dos cosas de la biblioteca Testing: import { render, screen } from '@testing-library/react'.

La renderfunción toma un componente React como parámetro y lo renderizará para que podamos probarlo.

screenes un objeto que viene con muchas consultas que podemos usar para probar la interfaz de usuario directamente, omitiendo los detalles de implementación y enfocándonos en lo que el usuario realmente verá.

  • Luego importamos nuestro Aboutcomponente:import About from './About'
  • Usamos las funciones describey testJest mencionadas anteriormente.
  • Renderizamos el Aboutcomponente:render( <About/> )
  • Usamos la expectfunción Jest, y como parámetro usamos el screenobjeto proporcionado por la biblioteca Testing. Usamos su getByTextconsulta, que escanea el componente React en busca del texto que pasamos como parámetro.
  • Para finalizar, usamos el comparador de la biblioteca .toBeInTheDocument()Testing, que solo verifica si se está procesando el resultado de la consulta anterior.

Luego podemos probar que el botón de alternar "Cambiar estado" funciona correctamente, así:

// About.test.jsx
import { render, screen } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import About from './About'

describe('About', () => {

  ...

  test('Switch state works correctly', async () => {
    render( <About/> )

    expect(screen.getByText("It's on!")).toBeInTheDocument()
    userEvent.click(screen.getByText('Switch state'))
    expect(screen.getByText("It's rolling!")).toBeInTheDocument()
    userEvent.click(screen.getByText('Switch state'))
    expect(screen.getByText("It's on!")).toBeInTheDocument()
  })

})

Vea que importamos una utilidad adicional llamada userEvent. Este es un objeto que contiene muchos métodos que podemos usar para simular eventos activados por el usuario, como clics, desplazamientos, escritura en una entrada, etc.

  • Primero verificamos que se represente la cadena predeterminada:expect(screen.getByText("It's on!")).toBeInTheDocument()
  • Luego simulamos un clic y comprobamos que la cadena cambia en la pantalla:
userEvent.click(screen.getByText('Switch state'))
expect(screen.getByText("It's rolling!")).toBeInTheDocument()
  • Y por último, simulamos otro clic y comprobamos que la cadena vuelve a su valor predeterminado:
userEvent.click(screen.getByText('Switch state'))
expect(screen.getByText("It's on!")).toBeInTheDocument()

Para terminar, vamos a escribir otra prueba para verificar que la entrada de texto y su alternancia funcionen correctamente.

import { render, screen } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import About from './About'

describe('About', () => {

  ...

  test('Input works correctly', async () => {
    render( <About/> )

    userEvent.type(screen.getByTestId("testInput"), "Testing the test")
    userEvent.click(screen.getByText("Print input"))

    expect(screen.getByText("Testing the test")).toBeInTheDocument()

    userEvent.click(screen.getByText("Print input"))
    expect(screen.queryByText("Testing the test")).not.toBeInTheDocument()
  })


})
  • Nuevamente usamos userEventpara simular el texto que se escribe en nuestro elemento de entrada:userEvent.type(screen.getByTestId("testInput"), "Testing the test")
  • Luego simulamos un clic en el botón de alternar y verificamos que el texto de entrada esté en el documento:
userEvent.click(screen.getByText("Print input"))
expect(screen.getByText("Testing the test")).toBeInTheDocument()
  • Y cerramos simulando otro clic y comprobando que la prueba ya no está presente:
userEvent.click(screen.getByText("Print input"))
expect(screen.getByText("Testing the test")).toBeInTheDocument()

Puede ver lo buenas que son las utilidades proporcionadas por las bibliotecas de prueba y lo fácil que es combinarlas con Jest. 🤓

Podemos ejecutar este archivo de prueba específico ejecutando npm test -- About.test.jsxy este es el resultado que obtenemos:

// console
PASS  src/components/About.test.jsx
  About
    ✓ About renders correctly (34 ms)
    ✓ Switch state works correctly (66 ms)
    ✓ Input works correctly (67 ms)

Test Suites: 1 passed, 1 total
Tests:       3 passed, 3 total
Snapshots:   0 total
Time:        0.997 s, estimated 1 s
Ran all test suites matching /About.test.jsx/i.

La última característica de Jest que me gustaría mostrarles es la cobertura de prueba .
Puede obtener un informe de cobertura ejecutando npm test -- --coverage.

Esto ejecutará sus pruebas normalmente y al final del informe de resultados debería ver algo como esto:

// console
...

----------------|---------|----------|---------|---------|-------------------
File            | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s 
----------------|---------|----------|---------|---------|-------------------
All files       |      75 |      100 |   85.71 |      70 |                   
 src            |       0 |      100 |       0 |       0 |                   
  App.jsx       |       0 |      100 |       0 |       0 | 7                 
  App.t.js      |       0 |        0 |       0 |       0 |                   
  index.js      |       0 |      100 |     100 |       0 | 5-6               
 src/components |     100 |      100 |     100 |     100 |                   
  About.jsx     |     100 |      100 |     100 |     100 |                   
----------------|---------|----------|---------|---------|-------------------

En el informe podemos ver que nuestro About.jsxcomponente está completamente cubierto, pero nuestros archivos App.jsxy no están siendo probados.index.js

Esta función es muy útil cuando se trabaja en grandes proyectos y desea saber rápidamente si la mayor parte de su código se está probando correctamente.

Cómo probar una aplicación React de front-end con Cypress

Hemos hablado mucho sobre Jest, así que ahora echemos un vistazo a cómo podemos probar nuestra aplicación usando Cypress.

Comenzaremos instalando Cypress ejecutando npm i -D cypress.

Esto debería agregar esto a nuestro package.json:

"devDependencies": {
    "cypress": "^9.5.4"
}

Entonces correremos npx cypress open. Esto abrirá el navegador Cypress y creará un cypressdirectorio dentro de nuestro proyecto. Dentro de este directorio encontrará ejemplos, documentación y opciones de configuración.

También encontrarás una carpeta de "integración", en la que tenemos que poner nuestras pruebas. Entonces, creemos nuestro About.test.jsarchivo en esa carpeta y repliquemos los mismos ejemplos de prueba que hemos visto con Jest:

// About.test.js
describe('AboutPage', () => {
    it('Renders correctly', () => {
        cy.visit('http://localhost:3000/about')
        cy.contains("I'm the about page!")
    })

    it('switch btn toggles text', () => {
        cy.contains("It's on!")
        cy.get('.switchBtn').click()
        cy.contains("It's rolling!")
        cy.get('.switchBtn').click()
        cy.contains("It's on!")
    })

    it('Input works correctly', () => {
        cy.get(".testInput").type("Testing the test")
        cy.get('.printInputBtn').click()
        cy.contains("Testing the test")

        cy.get('.printInputBtn').click()
        cy.contains("Testing the test").should('not.exist')
    })
})
  • La describefunción funciona igual que en broma.
  • it()es la misma que la test()función que hemos visto anteriormente.
  • En la primera prueba, le decimos al navegador que visite la URL de nuestra aplicación y verifique que se muestre el texto correspondiente:
cy.visit('http://localhost:3000/about')
cy.contains("I'm the about page!")
  • Luego verificamos que se represente el texto de alternancia predeterminado, simulamos un clic y verificamos que cambia en consecuencia:
cy.contains("It's on!")
cy.get('.switchBtn').click()
cy.contains("It's rolling!")
cy.get('.switchBtn').click()
cy.contains("It's on!")
  • Y para terminar, simulamos una entrada de texto, simulamos un clic y verificamos que el texto de entrada se represente:
cy.get(".testInput").type("Testing the test")
cy.get('.printInputBtn').click()
cy.contains("Testing the test")

cy.get('.printInputBtn').click()
cy.contains("Testing the test").should('not.exist')

La sintaxis es ligeramente diferente a Jest, pero la idea y la estructura son más o menos las mismas.🤙

Ahora si volvemos a ejecutar npx cypress open, debería abrirse una ventana con este contenido:
2022-04-23_22-30

Podemos hacer clic en "Ejecutar especificación de integración" y nuestra prueba se ejecutará automáticamente en el navegador simulado. Después de que se hayan ejecutado las pruebas, en el panel izquierdo veremos los resultados:
2022-04-23_22-31

Podemos abrir esos resultados para ver cada paso que ejecutó la prueba. Si pasamos el cursor sobre cada paso, lo veremos ejecutado en el navegador en tiempo real. Una característica realmente dulce de Cypress.👌👌

2022-04-23_22-34

Como puede ver, es muy fácil configurar pruebas con Cypress. Y si ya está familiarizado con Jest, puede retomarlo rápidamente ya que la sintaxis no es tan diferente.

Si se pregunta si tiene sentido usar Jest y Cypress como corredores de prueba en el mismo proyecto, creo que esta respuesta de desbordamiento de pila lo resume bastante bien.

Cómo probar una aplicación de nodo back-end

Ahora que tenemos una comprensión básica de las formas en que podemos probar una aplicación de front-end, crucemos el río y veamos cómo podemos usar herramientas similares para probar una aplicación de back-end.

Para esto, usaremos un Nodo simple y una API Express con solo 3 puntos finales.

Cree un directorio y ejecútelo npm init -ypara crear una aplicación Node. Ejecute npm i expresspara instalar Express y luego ejecute npm i -D jest supertestpara instalar tanto Jest como Supertest como dependencias de desarrollo.

Dentro de su package.json, agregue "scripts": { "test": "jest" }.
Su totalidad package.jsondebe verse así:

{
  "dependencies": {
    "express": "^4.17.3"
  },
  "devDependencies": {
    "jest": "^27.5.1",
    "supertest": "^6.2.2"
  },
    "scripts": {
    "test": "jest"
  }
}

Luego crea un app.jsarchivo y pon este código en él:

// app.js
/* Import and initialize express */
const express = require('express')
const app = express()
const server = require('http').Server(app)
/* Global middlewares */
app.use(express.json())

/* Endpoint 1 */
app.get('/', async (req, res) => {

    try {
        res.status(200).json({ greeting: "Hello there!" })
    } catch (err) {
        res.status(500).send(err)
    }
})

/* Endpoint 2 */
app.get('/isPalindrome', async (req, res) => {

    try {
        const string = req.body.string
        let result = true        
        let left = 0
        let right = string.length-1
        
        while (left < right && result) {
            if (string[left] === string[right]) {
                left += 1
                right -= 1
            }
            else result = false
        }
        
        res.status(200).json({ result: result })
        
    } catch (err) {
        res.status(500).send(err)
    }
})

/* Endpoint 3 */
app.get('/twoSum', async (req, res) => {
    
    try {
        const nums = JSON.parse(req.body.nums)
        const target = JSON.parse(req.body.target)

        let result = false
        
        for (let i = 0; i < nums.length; i++) {
            const neededNum = target - nums[i]
            if (nums.indexOf(neededNum) !== -1 && nums.indexOf(neededNum) !== i) result = [nums[i], nums[nums.indexOf(neededNum)]]
        }
        
        res.status(200).json({ result: result })
        
    } catch (err) {
        res.status(500).send(err)
    }
})

/* Export server object */
module.exports = server

/* Initialize server */
server.listen(3001, () => console.log('Server is listening.') )
server.on('error', error => console.error(error) )

Como puede ver, el extremo 1 solo devuelve un mensaje de saludo. Los extremos 2 y 3 son adaptaciones de las funciones que hemos visto en nuestros ejemplos de JS estándar. Ahora reciben los parámetros dentro de la solicitud y los valores de retorno van en la respuesta. 😉

¡Ahora las pruebas! Cree un app.test.jsarchivo y coloque este código dentro de él:

// app.test.js
const supertest = require('supertest') // Import supertest
const server = require("./app") // Import the server object
const requestWithSupertest = supertest(server) // We will use this function to mock HTTP requests

afterEach(done => { // afterEach function is provided by Jest and executes once all tests are finished
    server.close() // We close the server connection once all tests have finished
    done()
})

test('GET "/" returns greeting', async () => {
    const res = await requestWithSupertest.get('/')
    expect(res.status).toEqual(200)
    expect(res.type).toEqual(expect.stringContaining('json'))
    expect(res.body).toEqual({ greeting: "Hello there!" })
})

describe("/isPalindrome", () => {
    test('GET "/isPalindrome" neuquen returns true', async () => {
        const res = await requestWithSupertest.get('/isPalindrome').set('Content-type', 'application/json').send({ "string":"neuquen" })
        expect(res.status).toEqual(200)
        expect(res.type).toEqual(expect.stringContaining('json'))
        expect(res.body).toEqual({ result: true })
    })

    test('GET "/isPalindrome" bariloche returns true', async () => {
        const res = await requestWithSupertest.get('/isPalindrome').set('Content-type', 'application/json').send({ "string":"bariloche" })
        expect(res.status).toEqual(200)
        expect(res.type).toEqual(expect.stringContaining('json'))
        expect(res.body).toEqual({ result: false })
    })
})

describe("/twoSum", () => {
    test('GET "/twoSum" [2,7,11,15] and 9 returns [7, 2]', async () => {
        const res = await requestWithSupertest.get('/twoSum').set('Content-type', 'application/json').send({ "nums":"[2,7,11,15]", "target": "9" })
        expect(res.status).toEqual(200)
        expect(res.type).toEqual(expect.stringContaining('json'))
        expect(res.body).toEqual({ result: [7, 2] })
    })

    test('GET "/twoSum" [3,2,4] and 6 returns [4, 2]', async () => {
        const res = await requestWithSupertest.get('/twoSum').set('Content-type', 'application/json').send({ "nums":"[3,2,4]", "target": "6" })
        expect(res.status).toEqual(200)
        expect(res.type).toEqual(expect.stringContaining('json'))
        expect(res.body).toEqual({ result: [4, 2] })
    })

    test('GET "/twoSum" [3,2,4] and 10 returns false', async () => {
        const res = await requestWithSupertest.get('/twoSum').set('Content-type', 'application/json').send({ "nums":"[3,2,4]", "target": "10" })
        expect(res.status).toEqual(200)
        expect(res.type).toEqual(expect.stringContaining('json'))
        expect(res.body).toEqual({ result: false })
    })
})

Analicemos lo que estamos haciendo:

  • Nos burlamos de la solicitud conrequestWithSupertest.get('/')
  • Luego "rompemos" el resobjeto en pedazos y afirmamos cada parte del mismo:
    • Verifique el estado de la respuesta:expect(res.status).toEqual(200)
    • Revisa el formato de respuesta:expect(res.type).toEqual(expect.stringContaining('json'))
    • Compruebe el contenido del cuerpo de la respuesta:expect(res.body).toEqual({ greeting: "Hello there!" })

Las otras pruebas son muy similares, excepto que enviamos datos en los cuerpos de las solicitudes simuladas, así:

const res = await requestWithSupertest.get('/isPalindrome').set('Content-type', 'application/json').send({ "string":"bariloche" })

Como puede ver, probar de esta manera es realmente simple una vez que está familiarizado con Jest. Solo necesitamos un poco de ayuda de Supertest para simular la solicitud HTTP y el resto es solo afirmar la respuesta. 👏👏

Podemos ejecutar nuestras pruebas con npm testy deberíamos obtener la siguiente respuesta:

// console
 PASS  ./app.test.js
  ✓ GET "/" returns greeting (46 ms)
  /isPalindrome
    ✓ GET "/isPalindrome" neuquen returns true (18 ms)
    ✓ GET "/isPalindrome" bariloche returns true (3 ms)
  /twoSum
    ✓ GET "/twoSum" [2,7,11,15] and 9 returns [7, 2] (4 ms)
    ✓ GET "/twoSum" [3,2,4] and 6 returns [4, 2] (3 ms)
    ✓ GET "/twoSum" [3,2,4] and 10 returns false (2 ms)

Test Suites: 1 passed, 1 total
Tests:       6 passed, 6 total
Snapshots:   0 total
Time:        0.552 s, estimated 1 s
Ran all test suites.

Envolver

¡Y eso es! Hemos cubierto los conceptos básicos de cuatro herramientas muy populares que le permitirán probar tanto el front-end como el back-end de sus aplicaciones JS.

Por supuesto, hay mucho más en todas las herramientas que hemos visto y muchas características que no hemos cubierto. Pero la idea era darte una introducción para que puedas dar tus primeros pasos en el mundo del testing.

Como siempre, espero que hayas disfrutado el artículo y hayas aprendido algo nuevo. 

¡Salud y nos vemos en la próxima! =D

adiós--1- 

Fuente: https://www.freecodecamp.org/news/test-a-react-app-with-jest-testing-library-and-cypress/

#jest #testing #cypress #react 

Set Up Next.js Testing with Cypress, Jest, and React Testing Library

Learn how to set up Next.js with three commonly used testing tools: Cypress, Jest, and React Testing Library.

Cypress

Cypress is a test runner used for End-to-End (E2E) and Integration Testing.

Quickstart

You can use create-next-app with the with-cypress example to quickly get started.

npx create-next-app --example with-cypress with-cypress-app

Manual setup

To get started with Cypress, install the cypress package:

npm install --save-dev cypress

Add Cypress to the package.json scripts field:

"scripts": {
  "dev": "next dev",
  "build": "next build",
  "start": "next start",
  "cypress": "cypress open",
}

Run Cypress for the first time to generate examples that use their recommended folder structure:

npm run cypress

You can look through the generated examples and the Writing Your First Test section of the Cypress Documentation to help you get familiar with Cypress.

Creating your first Cypress integration test

Assuming the following two Next.js pages:

// pages/index.js
import Link from 'next/link'

export default function Home() {
  return (
    <nav>
      <Link href="/about">
        <a>About</a>
      </Link>
    </nav>
  )
}
// pages/about.js
export default function About() {
  return (
    <div>
      <h1>About Page</h1>
    </div>
  )
}

Add a test to check your navigation is working correctly:

// cypress/integration/app.spec.js

describe('Navigation', () => {
  it('should navigate to the about page', () => {
    // Start from the index page
    cy.visit('http://localhost:3000/')

    // Find a link with an href attribute containing "about" and click it
    cy.get('a[href*="about"]').click()

    // The new url should include "/about"
    cy.url().should('include', '/about')

    // The new page should contain an h1 with "About page"
    cy.get('h1').contains('About Page')
  })
})

You can use cy.visit("/") instead of cy.visit("http://localhost:3000/") if you add "baseUrl": "http://localhost:3000" to the cypress.json configuration file.

Running your Cypress tests

Since Cypress is testing a real Next.js application, it requires the Next.js server to be running prior to starting Cypress. We recommend running your tests against your production code to more closely resemble how your application will behave.

Run npm run build and npm run start, then run npm run cypress in another terminal window to start Cypress.

Note: Alternatively, you can install the start-server-and-test package and add it to the package.json scripts field: "test": "start-server-and-test start http://localhost:3000 cypress" to start the Next.js production server in conjuction with Cypress. Remember to rebuild your application after new changes.

Getting ready for Continuous Integration (CI)

You will have noticed that running Cypress so far has opened an interactive browser which is not ideal for CI environments. You can also run Cypress headlessly using the cypress run command:

// package.json

"scripts": {
  //...
  "cypress": "cypress open",
  "cypress:headless": "cypress run",
  "e2e": "start-server-and-test start http://localhost:3000 cypress",
  "e2e:headless": "start-server-and-test start http://localhost:3000 cypress:headless"
}

You can learn more about Cypress and Continuous Integration from these resources:

Jest and React Testing Library

Jest and React Testing Library are frequently used together for Unit Testing.

Quickstart

You can use create-next-app with the with-jest example to quickly get started with Jest and React Testing Library:

npx create-next-app --example with-jest with-jest-app

Manual setup

To manually set up Jest and React Testing Library, install jest , @testing-library/react, @testing-library/jest-dom as well as some supporting packages:

npm install --save-dev jest babel-jest @testing-library/react @testing-library/jest-dom identity-obj-proxy react-test-renderer

Configuring Jest

Create a jest.config.js file in your project's root directory and add the following configuration options:

// jest.config.js

module.exports = {
  collectCoverageFrom: [
    '**/*.{js,jsx,ts,tsx}',
    '!**/*.d.ts',
    '!**/node_modules/**',
  ],
  moduleNameMapper: {
    /* Handle CSS imports (with CSS modules)
    https://jestjs.io/docs/webpack#mocking-css-modules */
    '^.+\\.module\\.(css|sass|scss)$': 'identity-obj-proxy',

    // Handle CSS imports (without CSS modules)
    '^.+\\.(css|sass|scss)$': '<rootDir>/__mocks__/styleMock.js',

    /* Handle image imports
    https://jestjs.io/docs/webpack#handling-static-assets */
    '^.+\\.(jpg|jpeg|png|gif|webp|svg)$': '<rootDir>/__mocks__/fileMock.js',
  },
  testPathIgnorePatterns: ['<rootDir>/node_modules/', '<rootDir>/.next/'],
  testEnvironment: 'jsdom',
  transform: {
    /* Use babel-jest to transpile tests with the next/babel preset
    https://jestjs.io/docs/configuration#transform-objectstring-pathtotransformer--pathtotransformer-object */
    '^.+\\.(js|jsx|ts|tsx)$': ['babel-jest', { presets: ['next/babel'] }],
  },
  transformIgnorePatterns: [
    '/node_modules/',
    '^.+\\.module\\.(css|sass|scss)$',
  ],
}

You can learn more about each option above in the Jest docs.

Handling stylesheets and image imports

These files aren't useful in tests but importing them may cause errors, so we will need to mock them. Create the mock files we referenced in the configuration above - fileMock.js and styleMock.js - inside a __mocks__ directory:

// __mocks__/fileMock.js

(module.exports = "test-file-stub")
// __mocks__/styleMock.js

module.exports = {};

For more information on handling static assets, please refer to the Jest Docs.

Extend Jest with custom matchers

@testing-library/jest-dom includes a set of convenient custom matchers such as .toBeInTheDocument() making it easier to write tests. You can import the custom matchers for every test by adding the following option to the Jest configuration file:

// jest.config.js

setupFilesAfterEnv: ['<rootDir>/jest.setup.js']

Then, inside jest.setup.js, add the following import:

// jest.setup.js

import '@testing-library/jest-dom/extend-expect'

If you need to add more setup options before each test, it's common to add them to the jest.setup.js file above.

Absolute Imports and Module Path Aliases

If your project is using Module Path Aliases, you will need to configure Jest to resolve the imports by matching the paths option in the jsconfig.json file with the moduleNameMapper option in the jest.config.js file. For example:

// tsconfig.json or jsconfig.json
{
  "compilerOptions": {
    "baseUrl": ".",
    "paths": {
      "@/components/*": ["components/*"]
    }
  }
}
// jest.config.js
moduleNameMapper: {
  '^@/components/(.*)$': '<rootDir>/components/$1',
}

Add a test script to package.json

Add the Jest executable in watch mode to the package.json scripts:

"scripts": {
  "dev": "next dev",
  "build": "next build",
  "start": "next start",
  "test": "jest --watch"
}

jest --watch will re-run tests when a file is changed. For more Jest CLI options, please refer to the Jest Docs.

Create your first tests

Your project is now ready to run tests. Follow Jests convention by adding tests to the __tests__ folder in your project's root directory.

For example, we can add a test to check if the <Index /> component successfully renders a heading:

// __tests__/index.test.jsx

/**
 * @jest-environment jsdom
 */

import React from 'react'
import { render, screen } from '@testing-library/react'
import Home from '../pages/index'

describe('Home', () => {
  it('renders a heading', () => {
    render(<Home />)

    const heading = screen.getByRole('heading', {
      name: /welcome to next\.js!/i,
    })

    expect(heading).toBeInTheDocument()
  })
})

Note: The @jest-environment jsdom comment above configures the testing environment as jsdom inside the test file because React Testing Library uses DOM elements like document.body which will not work in Jest's default node testing environment. Alternatively, you can also set the jsdom environment globally by adding the Jest configuration option: "testEnvironment": "jsdom" in jest.config.js.

Optionally, add a snapshot test to keep track of any unexpected changes to your <Index /> component:

// __tests__/snapshot.js
import React from 'react'
import renderer from 'react-test-renderer'
import Index from '../pages/index'

it('renders homepage unchanged', () => {
  const tree = renderer.create(<Index />).toJSON()
  expect(tree).toMatchSnapshot()
})

Note: Test files should not be included inside the pages directory because any files inside the pages directory are considered routes.

Running your test suite

Run npm run jest to run your test suite. After your tests pass or fail, you will notice a list of interactive Jest commands that will be helpful as you add more tests.

#next #testing #cypress #jest #react #test #webdev 

How to Test Your Apps using Jest, Testing Library, Cypress, and Supertest

Hi everyone! In this article we're going to talk about testing. I'll give you a good overview of what testing is and an introduction of how you can implement it on your JavaScript projects. We'll use four very popular tools – Jest, Testing library, Cypress and Supertest.

First we're going to talk about what testing is, why is it a good idea to test our code, and the different kinds of tests that can be implemented.

Then we're going to introduce each of the tools we will be using, and finally we'll give practical examples for vanilla JS code, a front-end React app, and a back-end Node app.

Andiamo via!

What is Testing and Why is it Valuable

Testing is the practice of checking if a piece of software runs as expected. This is often recognized as QA or quality assurance, and aims to reduce to a bare minimum the amount of bugs that get to production.

We test software to identify errors, gaps or missing requirements and fix those things before shipping code to production.

Testing our code thoroughly improves our project's reliability, saves us later bug-fixing time and hence reduces costs, and improves the chance of our customer being fully satisfied with our product.

BvIJ1M5-1

Here's a nice short video by Fireship introducing some of the concepts we'll talk about later.

Different Types of Tests

Testing practices can be classified in different types according to many factors. Personally I think there's a lot of mumbo jumbo about this topic, with hundreds of terms that often refer to very similar things. So let's keep it simple and review only the most popular terms and what they mean.

This will help clarify the many ways in which a software can be tested and better understand the tools we're going to present later on.

Manual vs Automated testing

Depending on the tools we use to test our software, we can classify testing into manual or automated testing.

Manual testing is the practice of "clicking around" and manually checking all the features our product has, simulating what an actual user would do.

Automated testing is done through code, writing programs that check how our application runs.

There're many testing frameworks and libraries we can use for this. When it comes to functional testing (we're going to see what that means in a sec), most libraries work in a similar way:

  • First we define what piece of code we want to test.
  • Then we provide that piece of code some sort of input or execute an action on it.
  • Then we define what that piece of code should do given the input/action we performed.
  • And finally we will compare what that piece of code actually did against what we said it should do.

If it did what we said it should, the test passed. If it didn't, it failed.

Functional vs Non-functional testing

Functional testing refers to the actual features of our product. For example, if we have a blog platform, functional testing should assure the users can create new articles, edit those articles, browse through articles written by other people, and so on.

Non-functional testing refers to anything that's not strictly related to the core features of our product. And that again can be classified into different categories, for example:

  • Stress testing checks how infrastructure responds to heavy usage.
  • Security testing checks if an application is vulnerable to common hacking attacks.
  • Accessibility testing checks if an application is coded in a way that is accessible for people with different disabilities.

Unit vs Integration testing vs End-to-end testing

Another way to classify testing is depending how broad or comprehensive it is.

Unit testing aims to test individual functions, methods or small chunks of code in an independent way. In unit testing, small pieces of code are checked in an isolated way.

Integration testing checks how individual pieces of code interact with each other and work together. In integration testing, we put pieces together and see if they interact correctly.

End-to-end testing, also known as E2E, executes programs in a simulated environment that emulates actual user behavior. Having a website as an example, our code would open in an actual browser and all the features would be executed in the same way a user would use them. E2E testing is a lot like manual testing in that sense, but fully automated.

E2E testing is the most broad or comprehensive type of these three, as it evaluates whole features and behaviors, not specific parts of our code.

White box vs Black box vs Grey box testing

The last classification we're going to see depends on how much our tests focus on implementation details or user experience.

Let's say we have a simple website with a button that, when it gets clicked, it opens a modal. In our code, the button has a click event listener that executes a function. That function changes the CSS class of our modal HTML element, and that gets the modal rendered in the screen.

We talk about "white box" testing when we test implementation details. Following the example, under this paradigm we could test that the button click executes the corresponding function, and that after the function execution, the CSS class of our modal element is changed accordingly.

Another way to do this is to forget about implementation all together and just check if the modal is rendered after the button click. We don't care what the CSS class is, or if the corresponding function is executed or not. We just focus on testing what the user should perceive. That's "black box" testing.

And, as you may have guessed, "grey box" testing is just a combination of the previous two.

One last thing to mention here is that these different types of tests aren't necessarily mutually exclusive. I mean, they can and often are implemented at the same time on the same projects.

It's very common to have both manual and automated testing, functional and non-functional testing, unit and E2E testing ... The idea will always be to try to anticipate and solve the greatest possible number of problems in reasonable time and effort.

When to Test

This may seem like a simple question at first, but there are actually different approaches to this, too.

Some people like to test their app once it's been fully developed. Others like to write tests at the same time they code the application, and test each feature as it's being developed.

Others like to write tests first before anything else, defining in this way the minimum requirements for the program to accomplish. And then they code the app in a way that passes those tests as fast as possible (this is called test driven development or TDD).

Once you have an app or a whole feature developed, and you have a test suite in place (a test suite is a group of tests that check a particular feature or an entire app), another common practice is to run your tests each time you make any kind of modification to the codebase, to verify nothing gets broken.

Lastly, if you have a CI/CD system in place, it's common to automate the execution of tests before any deployment. So that if any test fails, the deployment is stopped and some kind of alert is dispatched (which of course is always better than seeing your app catch fire on prod 🔥😱).

Same as with test types, it's common to test applications at different times. Each company normally has its own testing schedule or practice to follow, tailored to their needs.

Our Toolset

Ok, now that we have a clearer idea of what testing is and the types of tests we can perform, let's review the tools we're going to use in our examples.

As mentioned before, there are a lot of different libraries to choose to run our tests. I chose these four because they are some of the most popular when it comes to JavaScript apps, but know there are more options out there. I'll be naming alternatives for most of the tools we'll use in case you'd like to investigate more. 😉

What is Jest

Jest is a JavaScript test-runner. A test-runner is a piece of software that allows you to run tests to evaluate your app. It's an open-source project maintained by Meta (formerly Facebook), and was first open-sourced in 2014.

Side comment: Every time I say "test runner" I picture this. Am I the only one? 🤔

8gTI-1

Test runner, not Blade runner!

Anyway...you can use Jest in projects that use Babel, TypeScript, Node.js, React, Angular, Vue.js, Svelte and other technologies too. You can install Jest through NPM just like any library and it requires very little configuration to start off.

Jest comes installed by default when setting up React apps with create-react-app.

Jest is often also called a testing framework, as it comes with many other built-in features besides just running tests (which is not the case with all test runners). Some of those features are:

  • Assertion library: Jest comes with a lot of built-in functions and methods you can use to assert your code (asserting basically means checking if a piece of code behaves like it's expected).
  • Snapshot testing: Jest allows you to use snapshots, which are a way of capturing a large object and storing it in memory so you can later on compare it with something else.
  • Code coverage: Jest allows you to get code coverage reports of your tests. These reports show what percentage of your code is currently being tested, and you can even see the exact lines of code that aren't currently being covered.
  • Mocking library: Jest also works like a mocking library in the sense that it allows you to mock data (like a function or a module) and use that in your tests.

Some well known alternatives to Jest are Mocha, Jasmine, and Karma.

Here's a nice little video explaining what Jest is.

What is Testing Library?

Testing library is not a test runner, but a set of utilities that will work together with a test runner like Jest or Mocha. This utilities are tools we can use to test our code easily and with a deeper focus on user experience (black box testing).

Testing library was developed by Kent C Dodds (who also happens to be one of the best JS teachers on earth, so I recommend that you follow him).

Quoting the official docs:

"The Testing Library family of libraries is a very light-weight solution for testing without all the implementation details.

The main utilities it provides involve querying for nodes similarly to how users would find them. In this way, testing-library helps ensure your tests give you confidence in your UI code."

In plain English, with the testing library we can test UI elements (like a paragraph, a button, a div...) instead of testing the code responsible for rendering the UI.

The principle behind the library is:

"The more your tests resemble the way your software is used, the more confidence they can give you."

... and that's exactly what we mean by "black box" testing. 😉

The testing library is actually a set of libraries, each created to achieve the same objective but adapted to work with different technologies such as React, Angular, Vue, Svelte, React Native and more... That's why you might hear "React-testing-library" or "Vue-testing-library". It's the same thing but adapted to work with different technologies.

React-testing-library comes installed by default when setting up React apps with create-react-app.

An alternative to testing library is Enzyme (a UI testing set of utilities developed by Airbnb).

What is Cypress?

Cypress is an open source test-runner that allows you to execute your projects in an automated browser, in the same way a user would.

With Cypress, we can program what the browser will do (like visit a URL, click a button, complete and submit a form...) and check that each action is matched with the corresponding response.

What's sweet about this is that the testing resembles A LOT to what the user will experience. And since the whole point of making software is the user, the closer we are to their perspective, the closer we should be to catching the most meaningful bugs in our code. (Plus it's really cool to see an automated browser go through your entire app in just a few seconds... 🤓)

Another nice feature of Cypress is "time travel". On Cypress's automated browser we can see all the test's we've written, and simply hover over them to see a graphical snapshot of its result. It's a very useful thing to better understand what's breaking and when.

Even though it can be used for unit and integration testing, Cypress is mostly used for end-to-end testing as it can easily evaluate complete features in a matter of seconds.

You can use Cypress to test anything that runs in a browser, so you can easily implement it on React, Angular, Vue, and so on.

Unlike Jest and React-Testing-Library, Cypress doesn't come pre-installed with create-react-app. But we can easily install it with NPM or your package manager of choice.

Some alternatives to Cypress are Selenium and Puppeteer.

Here's a sweet video by Fireship explaining what Cypress is and how it works.

Side comment: ...and every time I talk about Cypress this plays in my mind. 😎

What is Supertest?

Supertest is a library that simulates HTTP requests. It's super handy to test back-end Node apps together with Jest (as we will see in the coming examples).

Tools roundup

As a quick round-up about this topic:

  • Jest is the library that we'll use to write and run tests for JavaScript.
  • Testing library works together with Jest, and provides us with functions and methods to test the UI directly, forgetting about the code behind it.
  • Cypress runs your app in a simulated browser and checks if actions performed in the UI respond as expected.
  • Supertest is a library that mocks HTTP requests and it can be used together with Jest to test back-end apps.

Now let's begin with the fun part ...

giphy-2

Let the testing begin!!

How to Test Vanilla JS Code

Ok, let's start by testing some simple vanilla JS code. The idea here is to see how we can implement Jest in our project and learn the basics of how it works.

Let's start by creating a new directory in our machine and creating a Node app with npm init -y. Then install Jest by running npm i -D jest (-D saves it as a development dependency).

Now you should see something like this in your package.json file: "devDependencies": { "jest": "^27.5.1" } .

And speaking about it, in your package.json, replace your test script with "test": "jest". This will allow us to later run our tests by running npm test. ;)

Your entire package.json file should look something like this:

{
  "name": "vanillatesting",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "jest"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "jest": "^27.5.1"
  }
}

Cool, we're all set to write some JS we can actually test! Create an index.js file and put this code in it:

// index.js
function isPalindrome(string) {
    // O(n)
    // Put a pointer at each extreme of the word and iterate "inwards"
    // At each iteration, check if the pointers represent equal values
    // If this condition isn't accomplished, the word isn't a palindrome
    let left = 0
    let right = string.length-1
  
    while (left < right) {
        if (string[left] === string[right]) {
            left += 1
            right -= 1
        }
        else return false
    }
  
    return true
}

This function is a palindrome checker. It receives a string as parameter, and returns true if the string is a palindrome and false if it's not. (This is a classic technical interview question btw, but that's stuff for another article.🤫)

See that we're exporting the function too. Side comment: If you'd like to know more about this and how JS modules work, I recently wrote an article about it.

Great, so now let's test this function and see if it works as expected. Let's create a file called index.test.js.

This file is where we'll write our tests. The suffix we're using (.test.js) is important here, as Jest will automatically identify the .test files and execute them when we order Jest to test our project.

Jest also identifies files with the .spec suffix, like index.spec.js (for "specification", which refers to the requirements of your project). Personally I prefer .test as it feels more explicit to me, but both work the same.

Now let's write our first tests! Put this within your index.test.js file.

// index.test.js
isPalindrome = require('./index.js')

test('neuquen is palindrom', () => {
    expect(isPalindrome("neuquen")).toBe(true)
})

test('bariloche is not palindrom', () => {
    expect(isPalindrome("bariloche")).toBe(false)
})

Let's recap what we're actually doing:

  1. Require the function we want to test: isPalindrome = require('./index.js')
  2. The test() function is provided by Jest and within it we will put the code we want Jest to execute.
  3. test() takes two parameters. The first is a test description, which is a distinctive name that will show on our console when the test is run. We'll see an example in a sec.
  4. The second parameter is a callback, which contains the actual testing code.
  5. Within this callback we're calling the expect() function (also provided by Jest). expect() takes our function as parameter, which itself is receiving a parameter we made up.
  6. Last, we chain the .toBe() function (provided by Jest too) and as parameter we pass it the value we expect isPalindrome() to return for each case. ("neuquen" is a palindrome so our function should return true, and "bariloche" is not, so it should return false.)

One of the things I like the most about Jest is how easy it is to set it up. Another thing I like a lot is how self explanatory its syntax is. Notice that we can easily understand what our tests will evaluate by just reading them.👌

Now let's try this! If we run npm test in our console, we should get the following:

// console
> jest PASS 
./index.test.js
✓ neuquen is palindrom (1 ms)
✓ bariloche is not palindrom

Test Suites: 1 passed, 1
total Tests:       2 passed, 2
total Snapshots:   0
total Time:        0.244 s
Ran all test suites.

Congratulations, you just passed your first Jest test ever.

mr-miyagi-nod-1

lets-get-this-party-started-yeah-1

To see how a failing test looks too, let's change our function by editing the return lines.

// index.js
function isPalindrome(string) {
    // O(n)
    // Put a pointr at each extreme of the word and iterate "inwards"
    // At each iteration, check if the pointers represent equal values
    // If this condition isn't accomplished, the word isn't a palindrome
    let left = 0
    let right = string.length-1
  
    while (left < right) {
        if (string[left] === string[right]) {
            left += 1
            right -= 1
        }
        else return 1
    }
  
    return 2
}

Now you should get something like this:

// console
> vanillatesting@1.0.0 test
> jest

 FAIL  ./index.test.js
  ✕ neuquen is palindrom (4 ms)
  ✕ bariloche is not palindrom

  ● neuquen is palindrom

    expect(received).toBe(expected) // Object.is equality

    Expected: true
    Received: 2

      3 | // describe('isPalindrome function', () => {
      4 |   test('neuquen is palindrom', () => {
    > 5 |     expect(isPalindrome("neuquen")).toBe(true)
        |                                     ^
      6 |   })
      7 |
      8 |   test('bariloche is not palindrom', () => {

      at Object.<anonymous> (index.test.js:5:37)

  ● bariloche is not palindrom

    expect(received).toBe(expected) // Object.is equality

    Expected: false
    Received: 1

       7 |
       8 |   test('bariloche is not palindrom', () => {
    >  9 |     expect(isPalindrome("bariloche")).toBe(false)
         |                                       ^
      10 |   })
      11 | // })
      12 |

      at Object.<anonymous> (index.test.js:9:39)

Test Suites: 1 failed, 1 total
Tests:       2 failed, 2 total
Snapshots:   0 total
Time:        0.28 s, estimated 1 s
Ran all test suites.

See that you get a nice description of what tests failed and at which point they failed. In our case they failed when we asserted (checked) the return values.

This is very useful and we should always pay attention to these descriptions, as some times our tests may fail because they're not written correctly. And we don't normally write tests for our tests, yet... 😅 So when you see a failing test, first check that it's working as expected and then go review your actual code.

Now let's add and test another function to show some more Jest features:

// index.js
function twoSum(nums, target) {
    // O(n)
    // Iterate the array once
    // At each iteration, calculate the value needed to get to the target, which is target - currentValue
    // If the neededValue exists in the array, return [currentValue, neededValue], else continue iteration
	for (let i = 0; i < nums.length; i++) {
		const neededNum = target - nums[i]
		if (nums.indexOf(neededNum) !== -1 && nums.indexOf(neededNum) !== i) return [nums[i], nums[nums.indexOf(neededNum)]]
	}
    return false
}

module.exports = { isPalindrome, twoSum }

This is another classic interview question. The function takes two parameters, an array of numbers and a target value number. What it does is to identify if there are two numbers in the array that add up to the second parameter value. If the two values exist in the array, it returns them in an array, and if they don't, it returns false.

Now let's write some tests for this:

({ isPalindrome, twoSum } = require('./index.js'))

...

test('[2,7,11,15] and 9 returns [2, 7]', () => {
    expect(twoSum([2,7,11,15], 9)).toEqual([2,7])
})

test('[3,2,4] and 6 returns [2, 4]', () => {
    expect(twoSum([3,2,4], 6)).toEqual([2,4])
})

test('[3,2,4] and 10 returns false', () => {
    expect(twoSum([3,2,4], 10)).toBe(false)
})

See that the structure is almost the same, except we're using a different matcher in two of the tests, toEqual().

Matchers are the functions Jests provides us with to evaluate values. There are many types of matchers that can be used for many different occasions.

For example, .toBe() is used to evaluate primitives like strings, numbers, or booleans. toEqual() is used to evaluate objects (which covers pretty much everything else in Javascript).

If you need to compare the return value with a number you could use .toBeGreaterThan() or toBeGreaterThanOrEqual() and so on...

To see a full list of the available matchers, check the docs.

If we run our tests now, we will get the following:

> vanillatesting@1.0.0 test
> jest

 PASS  ./index.test.js
  ✓ neuquen is palindrom (2 ms)
  ✓ bariloche is not palindrom
  ✓ [2,7,11,15] and 9 returns [2, 7] (1 ms)
  ✓ [3,2,4] and 6 returns [2, 4]
  ✓ [3,2,4] and 10 returns false (1 ms)

Test Suites: 1 passed, 1 total
Tests:       5 passed, 5 total
Snapshots:   0 total
Time:        0.256 s, estimated 1 s
Ran all test suites.

That's cool, but our tests results look a bit messy. And as our tests suite grows, it will probably be harder to identify each separate result.

To help us with this, Jest provides us with a describe() function, which we can use to group tests together and show results in a more schematic way. We can use it like this:

({ isPalindrome, twoSum } = require('./index.js'))

describe('isPalindrome function', () => {
  test('neuquen is palindrom', () => {
    expect(isPalindrome("neuquen")).toBe(true)
  })

  test('bariloche is not palindrom', () => {
    expect(isPalindrome("bariloche")).toBe(false)
  })
})

describe('twoSum function', () => {
  test('[2,7,11,15] and 9 returns [2, 7]', () => {
    expect(twoSum([2,7,11,15], 9)).toEqual([2,7])
  })

  test('[3,2,4] and 6 returns [2, 4]', () => {
    expect(twoSum([3,2,4], 6)).toEqual([2,4])
  })

  test('[3,2,4] and 10 returns false', () => {
    expect(twoSum([3,2,4], 10)).toBe(false)
  })
})

The first parameter is the description we want to show for the given group of tests, and the second is a callback that contains our tests. Now if we run npm test again, we get this 😎:

// console
> vanillatesting@1.0.0 test
> jest

 PASS  ./index.test.js
  isPalindrome function
    ✓ neuquen is palindrom (2 ms)
    ✓ bariloche is not palindrom
  twoSum function
    ✓ [2,7,11,15] and 9 returns [2, 7] (1 ms)
    ✓ [3,2,4] and 6 returns [2, 4]
    ✓ [3,2,4] and 10 returns false

Test Suites: 1 passed, 1 total
Tests:       5 passed, 5 total
Snapshots:   0 total
Time:        0.216 s, estimated 1 s
Ran all test suites.

How to Test a Front-end React App with Jest and React Testing Library

Now that we know the basics of Jest, let's hop on to see how we can combine it with Testing library to test a React app.

For this we're going to use a dead simple example. Just a page with random text, a button that toggles another piece of text, a text input, and a button that toggles the rendering of the input.

Recording-2022-04-23-at-21.11.24

Take into account we'll be using create-react-app to create this app (which has Jest and Testing library installed by default). If you're not using create-react-app, you might need to install both libraries and add some extra config.

We're not going to see any React code here, we're just going to focus on the tests.

The folder structure of our project is the following:

> src
    > components
        - About.jsx
    - App.jsx
    - Index.js
    - setupTests.js

The setupTests.js file is important here. It's created by default with create-react-app with this content:

// jest-dom adds custom jest matchers for asserting on DOM nodes.
// allows you to do things like:
// expect(element).toHaveTextContent(/react/i)
// learn more: https://github.com/testing-library/jest-dom
import '@testing-library/jest-dom';

It globally imports the jest-dom library provided by Testing library, which gives us additional Jest matchers we can use to test the DOM (like toHaveTextContent(), toBeInTheDocument(), etc).

We're going to see examples in a bit, but know that some of the functions and matchers we'll use come from here.

Regarding our tests files, the common practice is to have a different test file for each component we're testing.

Regarding where to place them, two common practices are to have them all together in a single folder, like __tests__ or similar, or to have each test file in the same folder as the component it's testing.

I prefer the later as I'll often be hopping from the component code to the test code, and it's nice to have them nearby. But trully it doesn't matter. As long as we use the .test or .spec suffixes, Jest will identify and run the files anyway.

Having created our tests files, our folder structure should look like this:

> src
    > components
        - About.jsx
        - About.test.jsx
    - App.jsx
    - Index.js
    - setupTests.js

Cool! Let's start by testing our About component.

First lets test that it's rendering correctly, like this:

// About.test.jsx
import { render, screen } from '@testing-library/react'
import About from './About'

describe('About', () => {

  test('About renders correctly', () => {
    render( <About/> )
    expect(screen.getByText("I'm the about page!")).toBeInTheDocument()
  })

})
  • See that we start by importing two things from Testing library: import { render, screen } from '@testing-library/react'.

The render function takes a React component as a parameter and it will render it so we can test it.

screen is an object that comes with lots of queries we can use to test the UI directly, skipping implementation details and focusing on what the user will actually see.

  • Then we import our About component: import About from './About'
  • We use the describe and test Jest functions previously mentioned.
  • We render the About component: render( <About/> )
  • We use the expect Jest function, and as a parameter we use the screen object provided by Testing library. We use its getByText query, which scans the React component for the text we pass as parameter.
  • To end, we use the Testing library's .toBeInTheDocument() matcher, which just checks if the previous query result is being rendered.

Then we can test that the "Switch state" toggle button works correctly, like this:

// About.test.jsx
import { render, screen } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import About from './About'

describe('About', () => {

  ...

  test('Switch state works correctly', async () => {
    render( <About/> )

    expect(screen.getByText("It's on!")).toBeInTheDocument()
    userEvent.click(screen.getByText('Switch state'))
    expect(screen.getByText("It's rolling!")).toBeInTheDocument()
    userEvent.click(screen.getByText('Switch state'))
    expect(screen.getByText("It's on!")).toBeInTheDocument()
  })

})

See that we import an additional utility called userEvent. This is an object that contains many methods we can use to simulate user fired events, like clicks, hovers, writting in an input, and so on.

  • We first check that the default string is rendered: expect(screen.getByText("It's on!")).toBeInTheDocument()
  • Then we simulate a click and check that the string changes in the screen:
userEvent.click(screen.getByText('Switch state'))
expect(screen.getByText("It's rolling!")).toBeInTheDocument()
  • And last we simulate another click and check that the string reverses back to default:
userEvent.click(screen.getByText('Switch state'))
expect(screen.getByText("It's on!")).toBeInTheDocument()

To finish, we're going to write another test to verify that the text input and its toggle work correctly.

import { render, screen } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import About from './About'

describe('About', () => {

  ...

  test('Input works correctly', async () => {
    render( <About/> )

    userEvent.type(screen.getByTestId("testInput"), "Testing the test")
    userEvent.click(screen.getByText("Print input"))

    expect(screen.getByText("Testing the test")).toBeInTheDocument()

    userEvent.click(screen.getByText("Print input"))
    expect(screen.queryByText("Testing the test")).not.toBeInTheDocument()
  })


})
  • Again we use the userEvent to simulate text being writen into our input element: userEvent.type(screen.getByTestId("testInput"), "Testing the test")
  • Then we simulate a click on the toggle button, and check for the input text to be in the document:
userEvent.click(screen.getByText("Print input"))
expect(screen.getByText("Testing the test")).toBeInTheDocument()
  • And we close by simulating another click and checking that the test is no longer present:
userEvent.click(screen.getByText("Print input"))
expect(screen.getByText("Testing the test")).toBeInTheDocument()

You can see how nice the utilities provided by Testing libraries are, and how easy it is to combine them with Jest. 🤓

We can run this specific test file by running npm test -- About.test.jsx and this is the result we get:

// console
PASS  src/components/About.test.jsx
  About
    ✓ About renders correctly (34 ms)
    ✓ Switch state works correctly (66 ms)
    ✓ Input works correctly (67 ms)

Test Suites: 1 passed, 1 total
Tests:       3 passed, 3 total
Snapshots:   0 total
Time:        0.997 s, estimated 1 s
Ran all test suites matching /About.test.jsx/i.

The last Jest feature I'd like to show you is test coverage.
You can obtain a coverage report by running npm test -- --coverage.

This will run your tests normally and at the end of the results report you should see something like this:

// console
...

----------------|---------|----------|---------|---------|-------------------
File            | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s 
----------------|---------|----------|---------|---------|-------------------
All files       |      75 |      100 |   85.71 |      70 |                   
 src            |       0 |      100 |       0 |       0 |                   
  App.jsx       |       0 |      100 |       0 |       0 | 7                 
  App.t.js      |       0 |        0 |       0 |       0 |                   
  index.js      |       0 |      100 |     100 |       0 | 5-6               
 src/components |     100 |      100 |     100 |     100 |                   
  About.jsx     |     100 |      100 |     100 |     100 |                   
----------------|---------|----------|---------|---------|-------------------

In the report we can see that our About.jsx component is completely covered, but our App.jsx and index.js files are not being tested.

This feature is very handy when working on big projects and you want to quickly know if most of your code is being tested correctly.

How to Test a Front-end React App with Cypress

We've talked a lot about Jest, so now let's take a look at how we can test our app using Cypress.

We'll start off by installing Cypress by running npm i -D cypress.

This should add this to our package.json:

"devDependencies": {
    "cypress": "^9.5.4"
}

Then we'll run npx cypress open. This will open the Cypress browser, and create a cypress directory within our project. Within this directory you'll find examples, documentation, and config options.

You'llo also find an "integration" folder, in which we have to put our tests. So let's create our About.test.js file in that folder and replicate the same test examples we've seen with Jest:

// About.test.js
describe('AboutPage', () => {
    it('Renders correctly', () => {
        cy.visit('http://localhost:3000/about')
        cy.contains("I'm the about page!")
    })

    it('switch btn toggles text', () => {
        cy.contains("It's on!")
        cy.get('.switchBtn').click()
        cy.contains("It's rolling!")
        cy.get('.switchBtn').click()
        cy.contains("It's on!")
    })

    it('Input works correctly', () => {
        cy.get(".testInput").type("Testing the test")
        cy.get('.printInputBtn').click()
        cy.contains("Testing the test")

        cy.get('.printInputBtn').click()
        cy.contains("Testing the test").should('not.exist')
    })
})
  • The describe function works the same as in jest.
  • it() is the same as the test() function we've previously seen.
  • In the first test we tell the browser to visit our app's URL and check that the corresponding text is rendered:
cy.visit('http://localhost:3000/about')
cy.contains("I'm the about page!")
  • Then we check that the default toggle text is rendered, simulate a click and check that it changes accordingly:
cy.contains("It's on!")
cy.get('.switchBtn').click()
cy.contains("It's rolling!")
cy.get('.switchBtn').click()
cy.contains("It's on!")
  • And to end we simulate a text input, simulate a click, and check that the input text is rendered:
cy.get(".testInput").type("Testing the test")
cy.get('.printInputBtn').click()
cy.contains("Testing the test")

cy.get('.printInputBtn').click()
cy.contains("Testing the test").should('not.exist')

The syntaxt is slightly different than Jest, but the idea and structure are pretty much the same.🤙

Now if we run npx cypress open again, a window should open with this content:
2022-04-23_22-30

We can click on "Run integration spec" and our test will run automatically in the mock browser. After the tests have run, on the left panel we'll see the results:
2022-04-23_22-31

We can open those results to see each step the test executed. If we hover over each step, we'll see it executed in the browser in real time. A real sweet feature of Cypress.👌👌

2022-04-23_22-34

As you can see, it's very easy to set up tests with Cypress. And if you're already familiar with Jest, you can quickly pick it up as the syntax is not that different.

If you're wondering if it makes sense to use both Jest and Cypress as test runners in the same project, I think this stack-overflow answer sums it up quite nicely.

How to Test a Back-end Node App

Now that we have a basic understanding of the ways we can tests a front-end app, let's cross the river and see how we can use similar tools to test a back end app.

For this we'll use a simple Node and Express API with just 3 endpoints.

Create a directory and run npm init -y to create a Node app. Run npm i express to install Express, and then run npm i -D jest supertest to install both Jest and Supertest as development dependencies.

Inside your package.json, add "scripts": { "test": "jest" }.
Your entire package.json should look like this:

{
  "dependencies": {
    "express": "^4.17.3"
  },
  "devDependencies": {
    "jest": "^27.5.1",
    "supertest": "^6.2.2"
  },
    "scripts": {
    "test": "jest"
  }
}

Then create an app.js file and put this code in it:

// app.js
/* Import and initialize express */
const express = require('express')
const app = express()
const server = require('http').Server(app)
/* Global middlewares */
app.use(express.json())

/* Endpoint 1 */
app.get('/', async (req, res) => {

    try {
        res.status(200).json({ greeting: "Hello there!" })
    } catch (err) {
        res.status(500).send(err)
    }
})

/* Endpoint 2 */
app.get('/isPalindrome', async (req, res) => {

    try {
        const string = req.body.string
        let result = true        
        let left = 0
        let right = string.length-1
        
        while (left < right && result) {
            if (string[left] === string[right]) {
                left += 1
                right -= 1
            }
            else result = false
        }
        
        res.status(200).json({ result: result })
        
    } catch (err) {
        res.status(500).send(err)
    }
})

/* Endpoint 3 */
app.get('/twoSum', async (req, res) => {
    
    try {
        const nums = JSON.parse(req.body.nums)
        const target = JSON.parse(req.body.target)

        let result = false
        
        for (let i = 0; i < nums.length; i++) {
            const neededNum = target - nums[i]
            if (nums.indexOf(neededNum) !== -1 && nums.indexOf(neededNum) !== i) result = [nums[i], nums[nums.indexOf(neededNum)]]
        }
        
        res.status(200).json({ result: result })
        
    } catch (err) {
        res.status(500).send(err)
    }
})

/* Export server object */
module.exports = server

/* Initialize server */
server.listen(3001, () => console.log('Server is listening.') )
server.on('error', error => console.error(error) )

As you can see, endpoint 1 just returns a greeting message. Endpoint 2 and 3 are adaptations of the functions we've seen in our vanilla JS examples. They now receive the parameters within the request and the return values go in the response. 😉

Now the testing! Create an app.test.js file and put this code within it:

// app.test.js
const supertest = require('supertest') // Import supertest
const server = require("./app") // Import the server object
const requestWithSupertest = supertest(server) // We will use this function to mock HTTP requests

afterEach(done => { // afterEach function is provided by Jest and executes once all tests are finished
    server.close() // We close the server connection once all tests have finished
    done()
})

test('GET "/" returns greeting', async () => {
    const res = await requestWithSupertest.get('/')
    expect(res.status).toEqual(200)
    expect(res.type).toEqual(expect.stringContaining('json'))
    expect(res.body).toEqual({ greeting: "Hello there!" })
})

describe("/isPalindrome", () => {
    test('GET "/isPalindrome" neuquen returns true', async () => {
        const res = await requestWithSupertest.get('/isPalindrome').set('Content-type', 'application/json').send({ "string":"neuquen" })
        expect(res.status).toEqual(200)
        expect(res.type).toEqual(expect.stringContaining('json'))
        expect(res.body).toEqual({ result: true })
    })

    test('GET "/isPalindrome" bariloche returns true', async () => {
        const res = await requestWithSupertest.get('/isPalindrome').set('Content-type', 'application/json').send({ "string":"bariloche" })
        expect(res.status).toEqual(200)
        expect(res.type).toEqual(expect.stringContaining('json'))
        expect(res.body).toEqual({ result: false })
    })
})

describe("/twoSum", () => {
    test('GET "/twoSum" [2,7,11,15] and 9 returns [7, 2]', async () => {
        const res = await requestWithSupertest.get('/twoSum').set('Content-type', 'application/json').send({ "nums":"[2,7,11,15]", "target": "9" })
        expect(res.status).toEqual(200)
        expect(res.type).toEqual(expect.stringContaining('json'))
        expect(res.body).toEqual({ result: [7, 2] })
    })

    test('GET "/twoSum" [3,2,4] and 6 returns [4, 2]', async () => {
        const res = await requestWithSupertest.get('/twoSum').set('Content-type', 'application/json').send({ "nums":"[3,2,4]", "target": "6" })
        expect(res.status).toEqual(200)
        expect(res.type).toEqual(expect.stringContaining('json'))
        expect(res.body).toEqual({ result: [4, 2] })
    })

    test('GET "/twoSum" [3,2,4] and 10 returns false', async () => {
        const res = await requestWithSupertest.get('/twoSum').set('Content-type', 'application/json').send({ "nums":"[3,2,4]", "target": "10" })
        expect(res.status).toEqual(200)
        expect(res.type).toEqual(expect.stringContaining('json'))
        expect(res.body).toEqual({ result: false })
    })
})

Let's analyze what we're doing:

  • We mock the request with requestWithSupertest.get('/')
  • Then we "break" the res object in pieces and assert each part of it:
    • Check the response status: expect(res.status).toEqual(200)
    • Check the response format: expect(res.type).toEqual(expect.stringContaining('json'))
    • Check the response body content: expect(res.body).toEqual({ greeting: "Hello there!" })

The other tests are really similar, except we're sending data in the mock requests bodies, like this:

const res = await requestWithSupertest.get('/isPalindrome').set('Content-type', 'application/json').send({ "string":"bariloche" })

As you can see, testing in this way is really simple once you're familiar with Jest. We just need a little help by Supertest to mock the HTTP request and the rest is just asserting the response. 👏👏

We can run our tests with npm test and we should get the following response:

// console
 PASS  ./app.test.js
  ✓ GET "/" returns greeting (46 ms)
  /isPalindrome
    ✓ GET "/isPalindrome" neuquen returns true (18 ms)
    ✓ GET "/isPalindrome" bariloche returns true (3 ms)
  /twoSum
    ✓ GET "/twoSum" [2,7,11,15] and 9 returns [7, 2] (4 ms)
    ✓ GET "/twoSum" [3,2,4] and 6 returns [4, 2] (3 ms)
    ✓ GET "/twoSum" [3,2,4] and 10 returns false (2 ms)

Test Suites: 1 passed, 1 total
Tests:       6 passed, 6 total
Snapshots:   0 total
Time:        0.552 s, estimated 1 s
Ran all test suites.

Wrap up

And that's it! We've covered the basics of four very popular tools that will allow you to test both the front-end and back-end of your JS apps.

Of course there's much more to all the tools we've seen and many features we haven't covered. But the idea was to give you an introduction so you can take your first steps in the testing world.

As always, I hope you enjoyed the article and learned something new. 

Cheers and see you in the next one! =D

goodbye-bye--1-  

Source: https://www.freecodecamp.org/news/test-a-react-app-with-jest-testing-library-and-cypress/

#jest #testing #cypress 

Jest、Testing Library、Cypress、Supertestを使用してアプリをテストする

やあみんな!この記事では、テストについて説明します。テストとは何かの概要と、JavaScriptプロジェクトにテストを実装する方法を紹介します。Jest、Testingライブラリ、Cypress、Supertestの4つの非常に人気のあるツールを使用します。

最初に、テストとは何か、コードをテストするのが良い理由、および実装できるさまざまな種類のテストについて説明します。

次に、使用する各ツールを紹介し、最後に、バニラJSコード、フロントエンドReactアプリ、およびバックエンドNodeアプリの実用的な例を示します。

立ち去ろう!

テストとは何ですか、なぜそれは価値があるのですか

テストとは、ソフトウェアが期待どおりに動作するかどうかを確認する方法です。これは、QAまたは品質保証として認識されることが多く、本番環境に到達するバグの量を最小限に抑えることを目的としています。

ソフトウェアをテストして、エラー、ギャップ、または不足している要件を特定し、コードを本番環境に出荷する前にそれらを修正します。

コードを徹底的にテストすることで、プロジェクトの信頼性が向上し、後のバグ修正時間を節約できるため、コストが削減され、お客様が製品に完全に満足する可能性が高まります。

BvIJ1M5-1

これは、後で説明する概念のいくつかを紹介するFireshipによる素敵な短いビデオです。

さまざまな種類のテスト

テストの実践は、多くの要因に応じてさまざまなタイプに分類できます。個人的には、このトピックについては多くの巨大なジャンボがあり、非常によく似たものを指すことが多い何百もの用語があると思います。それでは、シンプルに保ち、最も人気のある用語とその意味だけを確認しましょう。

これは、ソフトウェアをテストできる多くの方法を明確にし、後で紹介するツールをよりよく理解するのに役立ちます。

手動テストと自動テスト

ソフトウェアのテストに使用するツールに応じて、テストを手動テストと自動テストに分類できます。

手動テストとは、「クリックして」製品のすべての機能を手動でチェックし、実際のユーザーが行うことをシミュレートすることです。

自動テストは、アプリケーションの実行方法をチェックするプログラムを記述して、コードを介して行われます。

これに使用できるテストフレームワークとライブラリはたくさんあります。機能テストに関しては(これが何を意味するのかをすぐに確認します)、ほとんどのライブラリは同じように機能します。

  • まず、テストするコードを定義します。
  • 次に、そのコードに何らかの入力を提供するか、そのコードに対してアクションを実行します。
  • 次に、実行した入力/アクションを前提として、そのコードが何をすべきかを定義します。
  • そして最後に、そのコードが実際に行ったことと、それが行うべきだと言ったことを比較します。

私たちが言うべきことをした場合、テストは合格しました。そうでなければ、失敗しました。

機能テストと非機能テスト

機能テストとは、当社製品の実際の機能を指します。たとえば、ブログプラットフォームがある場合、機能テストでは、ユーザーが新しい記事を作成したり、それらの記事を編集したり、他の人が書いた記事を閲覧したりできることを確認する必要があります。

非機能テストとは、当社の製品のコア機能に厳密に関連していないものを指します。また、これもさまざまなカテゴリに分類できます。たとえば、次のようになります。

  • ストレステストでは、インフラストラクチャが頻繁な使用にどのように対応するかを確認します。
  • セキュリティテストでは、アプリケーションが一般的なハッキング攻撃に対して脆弱かどうかを確認します。
  • アクセシビリティテストでは、さまざまな障害を持つ人々がアクセスできる方法でアプリケーションがコーディングされているかどうかを確認します。

ユニットvs統合テストvsエンドツーエンドテスト

テストを分類する別の方法は、テストの広さまたは包括性によって異なります。

単体テストは、個々の関数、メソッド、またはコードの小さなチャンクを独立した方法でテストすることを目的としています。単体テストでは、コードの小さな断片が分離された方法でチェックされます。

統合テストでは、個々のコードがどのように相互作用し、連携して機能するかをチェックします。統合テストでは、ピースをまとめて、正しく相互作用するかどうかを確認します。

E2Eとも呼ばれるエンドツーエンドのテストは、実際のユーザーの動作をエミュレートするシミュレートされた環境でプログラムを実行します。Webサイトを例にとると、コードは実際のブラウザーで開き、すべての機能はユーザーが使用するのと同じ方法で実行されます。E2Eテストは、その意味で手動テストによく似ていますが、完全に自動化されています。

E2Eテストは、コードの特定の部分ではなく、機能全体と動作を評価するため、これら3つの中で最も広範または包括的なタイプです。

ホワイトボックスvsブラックボックスvsグレーボックステスト

私たちが見る最後の分類は、テストが実装の詳細またはユーザーエクスペリエンスにどれだけ焦点を合わせているかによって異なります。

クリックするとモーダルを開くボタンのあるシンプルなWebサイトがあるとします。このコードでは、ボタンに関数を実行するクリックイベントリスナーがあります。この関数は、モーダルHTML要素のCSSクラスを変更し、画面にモーダルをレンダリングします。

実装の詳細をテストするときは、「ホワイトボックス」テストについて話します。この例に従って、このパラダイムの下で、ボタンのクリックによって対応する関数が実行され、関数の実行後にモーダル要素のCSSクラスがそれに応じて変更されることをテストできます。

これを行う別の方法は、実装をすべて一緒に忘れて、ボタンのクリック後にモーダルがレンダリングされるかどうかを確認することです。CSSクラスが何であるか、または対応する関数が実行されるかどうかは関係ありません。ユーザーが何を認識すべきかをテストすることに焦点を当てています。それが「ブラックボックス」テストです。

そして、ご想像のとおり、「グレーボックス」テストは前の2つの組み合わせにすぎません。

ここで最後に言及することは、これらの異なるタイプのテストは必ずしも相互に排他的ではないということです。つまり、同じプロジェクトで同時に実装することができ、多くの場合、それらを同時に実装することができます。

手動テストと自動テスト、機能テストと非機能テスト、ユニットテストとE2Eテストの両方を行うことは非常に一般的です...アイデアは常に、合理的な時間と労力で可能な限り多くの問題を予測して解決しようとすることです。

いつテストするか

これは最初は簡単な質問のように思えるかもしれませんが、実際にはこれにはさまざまなアプローチもあります。

アプリが完全に開発されたら、アプリをテストするのが好きな人もいます。他の人は、アプリケーションのコーディングと同時にテストを作成し、開発中に各機能をテストすることを好みます。

他の人は、何よりもまずテストを書き、プログラムが達成するための最小要件をこのように定義することを好みます。そして、それらのテストにできるだけ早く合格するようにアプリをコーディングします(これはテスト駆動開発またはTDDと呼ばれます)。

アプリまたは機能全体を開発し、テストスイートを配置したら(テストスイートは、特定の機能またはアプリ全体をチェックするテストのグループです)、別の一般的な方法は、毎回テストを実行することです。コードベースになんらかの変更を加えて、何も壊れていないことを確認します。

最後に、CI / CDシステムを導入している場合は、展開前にテストの実行を自動化するのが一般的です。そのため、テストが失敗した場合、デプロイは停止され、何らかのアラートが送信されます(もちろん、アプリが本番環境で発火するのを見るよりも常に優れています🔥😱)。

テストタイプの場合と同様に、さまざまな時間にアプリケーションをテストするのが一般的です。通常、各企業には、ニーズに合わせて、従うべき独自のテストスケジュールまたはプラクティスがあります。

ツールセット

さて、テストとは何か、実行できるテストの種類が明確になったので、例で使用するツールを確認しましょう。

前述のように、テストの実行を選択するためのさまざまなライブラリがあります。これらの4つを選んだのは、JavaScriptアプリに関して最も人気があるためですが、他にも選択肢があることを知っています。さらに調査したい場合に備えて、使用するほとんどのツールの代替案に名前を付けます。😉

ジェストとは

JestはJavaScriptのテストランナーです。テストランナーは、アプリを評価するためのテストを実行できるようにするソフトウェアです。これはMeta(以前のFacebook)によって維持されているオープンソースプロジェクトであり、2014年に最初にオープンソース化されました。

サイドコメント:「テストランナー」と言うたびに、これを思い描きます。私だけですか?🤔

8gTI-1

ブレードランナーではなく、テストランナー!

とにかく...Jestは、BabelTypeScriptNode.jsReactAngularVue.jsSvelteなどのテクノロジーを使用するプロジェクトでも使用できます。他のライブラリと同じようにNPMを介してJestをインストールでき、開始するのに必要な構成はほとんどありません。

create-react-appを使用してReactアプリを設定すると、Jestがデフォルトでインストールされます。

Jestは、テストを実行するだけでなく、他の多くの組み込み機能が付属しているため、テストフレームワークとも呼ばれます(すべてのテストランナーに当てはまるわけではありません)。それらの機能のいくつかは次のとおりです。

  • アサーションライブラリ: Jestには、コードをアサーションするために使用できる多くの組み込み関数とメソッドが付属しています(アサーションとは、基本的に、コードの一部が期待どおりに動作するかどうかを確認することを意味します)。
  • スナップショットテスト: Jestを使用すると、スナップショットを使用できます。スナップショットは、大きなオブジェクトをキャプチャしてメモリに保存する方法であり、後で他のオブジェクトと比較できます。
  • コードカバレッジ: Jestを使用すると、テストのコードカバレッジレポートを取得できます。これらのレポートは、コードの何パーセントが現在テストされているかを示し、現在カバーされていないコードの正確な行を確認することもできます。
  • モックライブラリ: Jestは、データ(関数やモジュールなど)をモックしてテストで使用できるという意味で、モックライブラリのようにも機能します。

Jestのよく知られた代替手段には、 MochaJasmine、およびKarmaがあります。

これは、 Jestが何であるかを説明する素敵な小さなビデオです。

テストライブラリとは何ですか?

テストライブラリはテストランナーではありませんが、JestやMochaなどのテストランナーと連携する一連のユーティリティです。このユーティリティは、コードを簡単にテストし、ユーザーエクスペリエンス(ブラックボックステスト)に重点を置いてテストするために使用できるツールです。

テストライブラリは、Kent C Dodds(地球上で最高のJS教師の1人でもあるため、彼をフォローすることをお勧めします)によって開発されました。

公式ドキュメントの引用:

「ライブラリのTestingLibraryファミリは、実装の詳細がすべてなくてもテストできる非常に軽量なソリューションです。

提供される主なユーティリティには、ユーザーがノードを見つけるのと同じようにノードをクエリすることが含まれます。このように、testing-libraryはテストを確実にするのに役立ちます。 UIコードに自信を持ってください。」

平易な英語では、テストライブラリを使用して、UIのレンダリングを担当するコードをテストする代わりに、UI要素(段落、ボタン、div ...など)をテストできます。

ライブラリの背後にある原則は次のとおりです。

「テストがソフトウェアの使用方法に似ているほど、信頼性が高まります。」

...そしてそれがまさに「ブラックボックス」テストの意味です。😉

テストライブラリは実際には一連のライブラリであり、それぞれが同じ目的を達成するために作成されていますが、React、Angular、Vue、Svelte、ReactNativeなどのさまざまなテクノロジーで動作するように適合されています...そのため「React-testing」と聞こえる場合があります-library」または「Vue-testing-library」。それは同じことですが、異なるテクノロジーで動作するように適合されています。

react-testing-libraryは、 create-react-appを使用してReactアプリを設定するときにデフォルトでインストールされます。

テストライブラリの代わりに、Enzyme(Airbnbによって開発されたユーティリティのUIテストセット)があります。

サイプレスとは?

Cypressは、ユーザーと同じように、自動化されたブラウザーでプロジェクトを実行できるオープンソースのテストランナーです。

Cypressを使用すると、ブラウザーの動作をプログラムして(URLにアクセスし、ボタンをクリックし、フォームに入力して送信するなど)、各アクションが対応する応答と一致することを確認できます。

これのすばらしい点は、テストがユーザーが体験するものと非常に似ていることです。そして、ソフトウェアを作成することの全体的なポイントはユーザーであるため、ユーザーの視点に近づくほど、コード内の最も意味のあるバグを見つけることに近づく必要があります。(さらに、自動化されたブラウザーがアプリ全体をわずか数秒で通過するのを見るのは本当にクールです...🤓)

サイプレスのもう一つの素晴らしい機能は「タイムトラベル」です。サイプレスの自動ブラウザでは、作成したすべてのテストを確認でき、それらにカーソルを合わせると、結果のグラフィカルなスナップショットが表示されます。何がいつ壊れているのかをよりよく理解することは非常に便利なことです。

ユニットテストと統合テストに使用できますが、サイプレスは、完全な機能を数秒で簡単に評価できるため、主にエンドツーエンドのテストに使用されます。

Cypressを使用して、ブラウザーで実行されるすべてのものをテストできるため、React、Angular、Vueなどに簡単に実装できます。

JestやReact-Testing-Libraryとは異なり、Cypressにはcreate-react-appがプリインストールされていません。ただし、NPMまたは選択したパッケージマネージャーを使用して簡単にインストールできます。

サイプレスのいくつかの代替品は、セレンパペッティアです。

これは、サイプレスとは何か、そしてそれがどのように機能するかを説明するFireshipによる素敵なビデオです。

サイドコメント:...そして私がサイプレスについて話すたびに、これは私の心の中で再生されます。😎

スーパーテストとは何ですか?

Supertestは、HTTPリクエストをシミュレートするライブラリです。Jestと一緒にバックエンドノードアプリをテストするのは非常に便利です(次の例で見るように)。

ツールのまとめ

このトピックに関する簡単なまとめとして:

  • Jestは、JavaScriptのテストを作成して実行するために使用するライブラリです。
  • テストライブラリはJestと連携して動作し、UIを直接テストするための関数とメソッドを提供し、その背後にあるコードを忘れます。
  • サイプレスは、シミュレートされたブラウザーでアプリを実行し、UIで実行されたアクションが期待どおりに応答するかどうかを確認します。
  • Supertestは、HTTPリクエストをモックするライブラリであり、Jestと一緒に使用してバックエンドアプリをテストできます。

それでは、楽しい部分から始めましょう...

giphy-2

テストを始めましょう!

バニラJSコードをテストする方法

さて、いくつかの簡単なバニラJSコードをテストすることから始めましょう。ここでの考え方は、プロジェクトにJestを実装する方法を確認し、その仕組みの基本を学ぶことです。

マシンに新しいディレクトリを作成し、を使用してノードアプリを作成することから始めましょうnpm init -y。次に、実行してJestをインストールしますnpm i -D jest-D開発依存関係として保存します)。

package.jsonこれで、ファイルに次のようなものが表示されるはずです"devDependencies": { "jest": "^27.5.1" }

そしてそれについて言えば、あなたの中で、あなたのスクリプトを。package.jsonに置き換えてください。これにより、後でを実行してテストを実行できるようになります。;)test"test": "jest"npm test

ファイル全体package.jsonは次のようになります。

{
  "name": "vanillatesting",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "jest"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "jest": "^27.5.1"
  }
}

かっこいい、実際にテストできるJSを作成する準備が整いました。ファイルを作成し、次のindex.jsコードをその中に入れます。

// index.js
function isPalindrome(string) {
    // O(n)
    // Put a pointer at each extreme of the word and iterate "inwards"
    // At each iteration, check if the pointers represent equal values
    // If this condition isn't accomplished, the word isn't a palindrome
    let left = 0
    let right = string.length-1
  
    while (left < right) {
        if (string[left] === string[right]) {
            left += 1
            right -= 1
        }
        else return false
    }
  
    return true
}

この関数は回文チェッカーです。パラメータとして文字列を受け取り、文字true列が回文であるfalseかどうかを返します。(これは古典的な技術面接の質問ですが、それは別の記事のためのものです。🤫)

関数もエクスポートしていることを確認してください。サイドコメント:これとJSモジュールがどのように機能するかについてもっと知りたい場合は、最近それについての記事を書きました。

それでは、この関数をテストして、期待どおりに機能するかどうかを確認しましょう。というファイルを作成しましょうindex.test.js

このファイルは、テストを作成する場所です。ここで使用しているサフィックス( )は重要です。これは、Jestにプロジェクトのテストを指示すると.test.js、Jestがファイルを自動的に識別して実行するためです。.test

Jestは、 (プロジェクトの要件を指す「仕様」の場合.spec)のように、接尾辞が付いたファイルも識別します。個人的には、より明確に感じるのでindex.spec.js好きですが、どちらも同じように機能します。.test

それでは、最初のテストを書いてみましょう。これをindex.test.jsファイル内に入れます。

// index.test.js
isPalindrome = require('./index.js')

test('neuquen is palindrom', () => {
    expect(isPalindrome("neuquen")).toBe(true)
})

test('bariloche is not palindrom', () => {
    expect(isPalindrome("bariloche")).toBe(false)
})

実際に行っていることを要約してみましょう。

  1. テストする関数が必要です。isPalindrome = require('./index.js')
  2. このtest()関数はJestによって提供され、その中にJestに実行させたいコードを配置します。
  3. test()2つのパラメータを取ります。1つ目はテストの説明です。これは、テストの実行時にコンソールに表示される固有の名前です。すぐに例を見ていきます。
  4. 2番目のパラメーターは、実際のテストコードを含むコールバックです。
  5. このコールバック内で、関数を呼び出していexpect()ます(これもJestによって提供されます)。expect()私たちの関数をパラメーターとして受け取り、それ自体が私たちが作成したパラメーターを受け取ります。
  6. 最後に、関数をチェーンし(Jestによって提供されます)、パラメーターとして、各ケースで返される.toBe()と予想される値を渡します。isPalindrome()(「ネウケン」は回文であるため、関数は戻る必要がありますがtrue、「バリローチェ」はそうではないため、戻る必要がありfalseます。)

Jestについて私が最も気に入っていることの1つは、セットアップがいかに簡単かということです。私がとても好きなもう一つのことは、その構文がいかに自明であるかということです。テストを読むだけで、テストの評価内容を簡単に理解できることに注意してください。👌

さあ、これを試してみましょう!コンソールで実行するnpm testと、次のようになります。

// console
> jest PASS 
./index.test.js
✓ neuquen is palindrom (1 ms)
✓ bariloche is not palindrom

Test Suites: 1 passed, 1
total Tests:       2 passed, 2
total Snapshots:   0
total Time:        0.244 s
Ran all test suites.

おめでとうございます、あなたは初めてのJestテストに合格しました。

mr-miyagi-nod-1

Lets-get-this-party-started-yeah-1

失敗したテストもどのように見えるかを確認するために、return行を編集して関数を変更してみましょう。

// index.js
function isPalindrome(string) {
    // O(n)
    // Put a pointr at each extreme of the word and iterate "inwards"
    // At each iteration, check if the pointers represent equal values
    // If this condition isn't accomplished, the word isn't a palindrome
    let left = 0
    let right = string.length-1
  
    while (left < right) {
        if (string[left] === string[right]) {
            left += 1
            right -= 1
        }
        else return 1
    }
  
    return 2
}

今、あなたはこのようなものを手に入れるはずです:

// console
> vanillatesting@1.0.0 test
> jest

 FAIL  ./index.test.js
  ✕ neuquen is palindrom (4 ms)
  ✕ bariloche is not palindrom

  ● neuquen is palindrom

    expect(received).toBe(expected) // Object.is equality

    Expected: true
    Received: 2

      3 | // describe('isPalindrome function', () => {
      4 |   test('neuquen is palindrom', () => {
    > 5 |     expect(isPalindrome("neuquen")).toBe(true)
        |                                     ^
      6 |   })
      7 |
      8 |   test('bariloche is not palindrom', () => {

      at Object.<anonymous> (index.test.js:5:37)

  ● bariloche is not palindrom

    expect(received).toBe(expected) // Object.is equality

    Expected: false
    Received: 1

       7 |
       8 |   test('bariloche is not palindrom', () => {
    >  9 |     expect(isPalindrome("bariloche")).toBe(false)
         |                                       ^
      10 |   })
      11 | // })
      12 |

      at Object.<anonymous> (index.test.js:9:39)

Test Suites: 1 failed, 1 total
Tests:       2 failed, 2 total
Snapshots:   0 total
Time:        0.28 s, estimated 1 s
Ran all test suites.

どのテストが失敗し、どの時点で失敗したかについての適切な説明が得られることを確認してください。私たちの場合、戻り値をアサート(チェック)したときに失敗しました。

これは非常に便利であり、正しく記述されていないためにテストが失敗する場合があるため、これらの説明には常に注意を払う必要があります。また、通常はテスト用のテストを作成していませんが...😅テストが失敗した場合は、最初にテストが期待どおりに機能していることを確認してから、実際のコードを確認してください。

次に、別の関数を追加してテストし、Jestの機能をさらにいくつか表示しましょう。

// index.js
function twoSum(nums, target) {
    // O(n)
    // Iterate the array once
    // At each iteration, calculate the value needed to get to the target, which is target - currentValue
    // If the neededValue exists in the array, return [currentValue, neededValue], else continue iteration
	for (let i = 0; i < nums.length; i++) {
		const neededNum = target - nums[i]
		if (nums.indexOf(neededNum) !== -1 && nums.indexOf(neededNum) !== i) return [nums[i], nums[nums.indexOf(neededNum)]]
	}
    return false
}

module.exports = { isPalindrome, twoSum }

これは別の古典的なインタビューの質問です。この関数は、数値の配列とターゲット値の数値の2つのパラメーターを取ります。これは、配列内に2番目のパラメーター値を合計する2つの数値があるかどうかを識別することです。2つの値が配列に存在する場合は配列で返し、存在しない場合はfalseを返します。

それでは、これについていくつかのテストを書いてみましょう。

({ isPalindrome, twoSum } = require('./index.js'))

...

test('[2,7,11,15] and 9 returns [2, 7]', () => {
    expect(twoSum([2,7,11,15], 9)).toEqual([2,7])
})

test('[3,2,4] and 6 returns [2, 4]', () => {
    expect(twoSum([3,2,4], 6)).toEqual([2,4])
})

test('[3,2,4] and 10 returns false', () => {
    expect(twoSum([3,2,4], 10)).toBe(false)
})

2つのテストで異なるマッチャーを使用していることを除いて、構造がほぼ同じであることを確認してくださいtoEqual()

マッチャーは、Jestsが値を評価するために提供する関数です。さまざまな場面で使用できるマッチャーには多くの種類があります。

たとえば、.toBe()文字列、数値、ブール値などのプリミティブを評価するために使用されます。toEqual()オブジェクトを評価するために使用されます(Javascriptの他のほとんどすべてをカバーします)。

戻り値を使用できる数値.toBeGreaterThan()などと比較する必要がある場合toBeGreaterThanOrEqual()は...

利用可能なマッチャーの完全なリストを確認するには、ドキュメントを確認してください。

ここでテストを実行すると、次のようになります。

> vanillatesting@1.0.0 test
> jest

 PASS  ./index.test.js
  ✓ neuquen is palindrom (2 ms)
  ✓ bariloche is not palindrom
  ✓ [2,7,11,15] and 9 returns [2, 7] (1 ms)
  ✓ [3,2,4] and 6 returns [2, 4]
  ✓ [3,2,4] and 10 returns false (1 ms)

Test Suites: 1 passed, 1 total
Tests:       5 passed, 5 total
Snapshots:   0 total
Time:        0.256 s, estimated 1 s
Ran all test suites.

かっこいいですが、テスト結果は少し乱雑に見えます。そして、私たちのテストスイートが成長するにつれて、それぞれの個別の結果を識別するのはおそらく難しくなるでしょう。

これを支援するために、Jestは関数を提供します。このdescribe()関数を使用して、テストをグループ化し、より概略的な方法で結果を表示できます。次のように使用できます。

({ isPalindrome, twoSum } = require('./index.js'))

describe('isPalindrome function', () => {
  test('neuquen is palindrom', () => {
    expect(isPalindrome("neuquen")).toBe(true)
  })

  test('bariloche is not palindrom', () => {
    expect(isPalindrome("bariloche")).toBe(false)
  })
})

describe('twoSum function', () => {
  test('[2,7,11,15] and 9 returns [2, 7]', () => {
    expect(twoSum([2,7,11,15], 9)).toEqual([2,7])
  })

  test('[3,2,4] and 6 returns [2, 4]', () => {
    expect(twoSum([3,2,4], 6)).toEqual([2,4])
  })

  test('[3,2,4] and 10 returns false', () => {
    expect(twoSum([3,2,4], 10)).toBe(false)
  })
})

最初のパラメーターは、特定のテストグループに対して表示する説明であり、2番目のパラメーターは、テストを含むコールバックです。もう一度実行npm testすると、次のようになります😎:

// console
> vanillatesting@1.0.0 test
> jest

 PASS  ./index.test.js
  isPalindrome function
    ✓ neuquen is palindrom (2 ms)
    ✓ bariloche is not palindrom
  twoSum function
    ✓ [2,7,11,15] and 9 returns [2, 7] (1 ms)
    ✓ [3,2,4] and 6 returns [2, 4]
    ✓ [3,2,4] and 10 returns false

Test Suites: 1 passed, 1 total
Tests:       5 passed, 5 total
Snapshots:   0 total
Time:        0.216 s, estimated 1 s
Ran all test suites.

JestおよびReactテストライブラリを使用してフロントエンドReactアプリをテストする方法

Jestの基本がわかったので、JestをTestingライブラリと組み合わせてReactアプリをテストする方法を見てみましょう。

このために、私たちは死んだ単純な例を使用するつもりです。ランダムなテキスト、別のテキストを切り替えるボタン、テキスト入力、および入力のレンダリングを切り替えるボタンを含むページだけです。

録音-2022-04-23-at-21.11.24

create-react-appを使用してこのアプリを作成することを考慮してください(デフォルトでJestおよびTestingライブラリがインストールされています)。create-react-appを使用していない場合は、両方のライブラリをインストールして、構成を追加する必要がある場合があります。

ここではReactコードは表示されません。ここでは、テストに焦点を当てます。

プロジェクトのフォルダ構造は次のとおりです。

> src
    > components
        - About.jsx
    - App.jsx
    - Index.js
    - setupTests.js

ここsetupTests.jsでファイルは重要です。これは、デフォルトで次のコンテンツを含むcreate-react-appで作成されます。

// jest-dom adds custom jest matchers for asserting on DOM nodes.
// allows you to do things like:
// expect(element).toHaveTextContent(/react/i)
// learn more: https://github.com/testing-library/jest-dom
import '@testing-library/jest-dom';

jest-domTestingライブラリによって提供されるライブラリをグローバルにインポートします。これにより、DOMのテストに使用できる追加のJestマッチャー(toHaveTextContent(), toBeInTheDocument()など)が提供されます。

例を少し見ていきますが、使用する関数とマッチャーの一部はここからのものであることを知っています。

テストファイルに関しては、一般的な方法は、テストするコンポーネントごとに異なるテストファイルを用意することです。

それらを配置する場所に関して、2つの一般的な方法は、それらをすべて1つのフォルダーにまとめる、__tests__または同様のものにするか、各テストファイルをテストするコンポーネントと同じフォルダーに置くことです。

コンポーネントコードからテストコードに頻繁に移動するので、後者の方が好きです。近くにあると便利です。しかし、本当にそれは問題ではありません。.testまたはサフィックスを使用している限り.spec、Jestはファイルを識別して実行します。

テストファイルを作成すると、フォルダー構造は次のようになります。

> src
    > components
        - About.jsx
        - About.test.jsx
    - App.jsx
    - Index.js
    - setupTests.js

涼しい!Aboutコンポーネントのテストから始めましょう。

まず、次のように、正しくレンダリングされていることをテストします。

// About.test.jsx
import { render, screen } from '@testing-library/react'
import About from './About'

describe('About', () => {

  test('About renders correctly', () => {
    render( <About/> )
    expect(screen.getByText("I'm the about page!")).toBeInTheDocument()
  })

})
  • テストライブラリから2つのものをインポートすることから始めることを確認してくださいimport { render, screen } from '@testing-library/react'

このrender関数はReactコンポーネントをパラメーターとして受け取り、それをレンダリングしてテストできるようにします。

screenは、UIを直接テストするために使用できる多くのクエリが付属しているオブジェクトであり、実装の詳細をスキップして、ユーザーに実際に表示される内容に焦点を当てています。

  • About次に、コンポーネントをインポートします。import About from './About'
  • 前述のdescribeおよびJest関数を使用します。test
  • Aboutコンポーネントをレンダリングします。render( <About/> )
  • expectJest関数を使用し、パラメーターとしてscreenTestingライブラリによって提供されるオブジェクトを使用します。そのgetByTextクエリを使用して、Reactコンポーネントをスキャンし、パラメーターとして渡すテキストを探します。
  • 最後に、テストライブラリの.toBeInTheDocument()マッチャーを使用します。これは、前のクエリ結果がレンダリングされているかどうかをチェックするだけです。

次に、次のように、[状態の切り替え]トグルボタンが正しく機能することをテストできます。

// About.test.jsx
import { render, screen } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import About from './About'

describe('About', () => {

  ...

  test('Switch state works correctly', async () => {
    render( <About/> )

    expect(screen.getByText("It's on!")).toBeInTheDocument()
    userEvent.click(screen.getByText('Switch state'))
    expect(screen.getByText("It's rolling!")).toBeInTheDocument()
    userEvent.click(screen.getByText('Switch state'))
    expect(screen.getByText("It's on!")).toBeInTheDocument()
  })

})

と呼ばれる追加のユーティリティをインポートすることを確認してくださいuserEvent。これは、クリック、ホバー、入力の書き込みなど、ユーザーが発生したイベントをシミュレートするために使用できる多くのメソッドを含むオブジェクトです。

  • まず、デフォルトの文字列がレンダリングされていることを確認します。expect(screen.getByText("It's on!")).toBeInTheDocument()
  • 次に、クリックをシミュレートし、画面内で文字列が変化することを確認します。
userEvent.click(screen.getByText('Switch state'))
expect(screen.getByText("It's rolling!")).toBeInTheDocument()
  • 最後に、別のクリックをシミュレートして、文字列がデフォルトに戻ることを確認します。
userEvent.click(screen.getByText('Switch state'))
expect(screen.getByText("It's on!")).toBeInTheDocument()

最後に、テキスト入力とそのトグルが正しく機能することを確認するための別のテストを作成します。

import { render, screen } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import About from './About'

describe('About', () => {

  ...

  test('Input works correctly', async () => {
    render( <About/> )

    userEvent.type(screen.getByTestId("testInput"), "Testing the test")
    userEvent.click(screen.getByText("Print input"))

    expect(screen.getByText("Testing the test")).toBeInTheDocument()

    userEvent.click(screen.getByText("Print input"))
    expect(screen.queryByText("Testing the test")).not.toBeInTheDocument()
  })


})
  • ここでも、を使用しuserEventて、入力要素に書き込まれるテキストをシミュレートします。userEvent.type(screen.getByTestId("testInput"), "Testing the test")
  • 次に、トグルボタンのクリックをシミュレートし、入力テキストがドキュメントに含まれているかどうかを確認します。
userEvent.click(screen.getByText("Print input"))
expect(screen.getByText("Testing the test")).toBeInTheDocument()
  • そして、別のクリックをシミュレートし、テストが存在しないことを確認して終了します。
userEvent.click(screen.getByText("Print input"))
expect(screen.getByText("Testing the test")).toBeInTheDocument()

テストライブラリによって提供されるユーティリティがいかに優れているか、そしてそれらをJestと組み合わせることがいかに簡単であるかを見ることができます。🤓

を実行することでこの特定のテストファイルを実行できますnpm test -- About.test.jsx。これが次の結果になります。

// console
PASS  src/components/About.test.jsx
  About
    ✓ About renders correctly (34 ms)
    ✓ Switch state works correctly (66 ms)
    ✓ Input works correctly (67 ms)

Test Suites: 1 passed, 1 total
Tests:       3 passed, 3 total
Snapshots:   0 total
Time:        0.997 s, estimated 1 s
Ran all test suites matching /About.test.jsx/i.

最後に紹介したいJestの機能は、テストカバレッジです。
を実行すると、カバレッジレポートを取得できますnpm test -- --coverage

これにより、テストが正常に実行され、結果レポートの最後に次のように表示されます。

// console
...

----------------|---------|----------|---------|---------|-------------------
File            | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s 
----------------|---------|----------|---------|---------|-------------------
All files       |      75 |      100 |   85.71 |      70 |                   
 src            |       0 |      100 |       0 |       0 |                   
  App.jsx       |       0 |      100 |       0 |       0 | 7                 
  App.t.js      |       0 |        0 |       0 |       0 |                   
  index.js      |       0 |      100 |     100 |       0 | 5-6               
 src/components |     100 |      100 |     100 |     100 |                   
  About.jsx     |     100 |      100 |     100 |     100 |                   
----------------|---------|----------|---------|---------|-------------------

レポートでは、About.jsxコンポーネントが完全にカバーされていることがわかりますが、ファイルApp.jsxindex.jsファイルはテストされていません。

この機能は、大規模なプロジェクトで作業する場合に非常に便利であり、コードのほとんどが正しくテストされているかどうかをすばやく知りたい場合があります。

サイプレスでフロントエンドReactアプリをテストする方法

Jestについてはたくさん話しましたが、サイプレスを使用してアプリをテストする方法を見てみましょう。

を実行してサイプレスをインストールすることから始めますnpm i -D cypress

これは私たちにこれを追加する必要がありますpackage.json

"devDependencies": {
    "cypress": "^9.5.4"
}

次に、を実行しますnpx cypress open。これにより、サイプレスブラウザが開き、cypressプロジェクト内にディレクトリが作成されます。このディレクトリ内には、例、ドキュメント、および構成オプションがあります。

また、テストを配置する必要のある「統合」フォルダーもあります。About.test.jsそれでは、そのフォルダーにファイルを作成し、Jestで見たのと同じテスト例を複製しましょう。

// About.test.js
describe('AboutPage', () => {
    it('Renders correctly', () => {
        cy.visit('http://localhost:3000/about')
        cy.contains("I'm the about page!")
    })

    it('switch btn toggles text', () => {
        cy.contains("It's on!")
        cy.get('.switchBtn').click()
        cy.contains("It's rolling!")
        cy.get('.switchBtn').click()
        cy.contains("It's on!")
    })

    it('Input works correctly', () => {
        cy.get(".testInput").type("Testing the test")
        cy.get('.printInputBtn').click()
        cy.contains("Testing the test")

        cy.get('.printInputBtn').click()
        cy.contains("Testing the test").should('not.exist')
    })
})
  • 関数はdescribejestと同じように機能します。
  • it()test()これまでに見た関数と同じです。
  • 最初のテストでは、ブラウザにアプリのURLにアクセスして、対応するテキストがレンダリングされることを確認するように指示します。
cy.visit('http://localhost:3000/about')
cy.contains("I'm the about page!")
  • 次に、デフォルトのトグルテキストがレンダリングされていることを確認し、クリックをシミュレートして、それに応じて変更されることを確認します。
cy.contains("It's on!")
cy.get('.switchBtn').click()
cy.contains("It's rolling!")
cy.get('.switchBtn').click()
cy.contains("It's on!")
  • 最後に、テキスト入力をシミュレートし、クリックをシミュレートして、入力テキストがレンダリングされることを確認します。
cy.get(".testInput").type("Testing the test")
cy.get('.printInputBtn').click()
cy.contains("Testing the test")

cy.get('.printInputBtn').click()
cy.contains("Testing the test").should('not.exist')

構文はJestとは少し異なりますが、アイデアと構造はほとんど同じです。🤙

ここでnpx cypress open再度実行すると、次のコンテンツを含むウィンドウが開きます。
2022-04-23_22-30

「統合仕様の実行」をクリックすると、模擬ブラウザでテストが自動的に実行されます。テストが実行された後、左側のパネルに結果が表示されます。
2022-04-23_22-31

これらの結果を開いて、テストが実行された各ステップを確認できます。各ステップにカーソルを合わせると、ブラウザでリアルタイムに実行されていることがわかります。サイプレスの本当に甘い機能。👌👌

2022-04-23_22-34

ご覧のとおり、サイプレスを使用してテストを設定するのは非常に簡単です。また、Jestに既に精通している場合は、構文にそれほど違いがないため、すぐに理解できます。

同じプロジェクトでJestとCypressの両方をテストランナーとして使用することが理にかなっているのかどうか疑問に思っている場合は、このスタックオーバーフローの回答が非常にうまくまとめられていると思います。

バックエンドノードアプリをテストする方法

フロントエンドアプリをテストする方法の基本を理解したので、川を渡って、同様のツールを使用してバックエンドアプリをテストする方法を見てみましょう。

このために、3つのエンドポイントを持つ単純なノードとExpressAPIを使用します。

ディレクトリをnpm init -y作成し、実行してノードアプリを作成します。npm i expressExpressをインストールするために実行しnpm i -D jest supertestてから、開発の依存関係としてJestとSupertestの両方をインストールするために実行します。

の中にpackage.json、を追加し"scripts": { "test": "jest" }ます。
全体package.jsonは次のようになります。

{
  "dependencies": {
    "express": "^4.17.3"
  },
  "devDependencies": {
    "jest": "^27.5.1",
    "supertest": "^6.2.2"
  },
    "scripts": {
    "test": "jest"
  }
}

次に、app.jsファイルを作成し、このコードをその中に入れます。

// app.js
/* Import and initialize express */
const express = require('express')
const app = express()
const server = require('http').Server(app)
/* Global middlewares */
app.use(express.json())

/* Endpoint 1 */
app.get('/', async (req, res) => {

    try {
        res.status(200).json({ greeting: "Hello there!" })
    } catch (err) {
        res.status(500).send(err)
    }
})

/* Endpoint 2 */
app.get('/isPalindrome', async (req, res) => {

    try {
        const string = req.body.string
        let result = true        
        let left = 0
        let right = string.length-1
        
        while (left < right && result) {
            if (string[left] === string[right]) {
                left += 1
                right -= 1
            }
            else result = false
        }
        
        res.status(200).json({ result: result })
        
    } catch (err) {
        res.status(500).send(err)
    }
})

/* Endpoint 3 */
app.get('/twoSum', async (req, res) => {
    
    try {
        const nums = JSON.parse(req.body.nums)
        const target = JSON.parse(req.body.target)

        let result = false
        
        for (let i = 0; i < nums.length; i++) {
            const neededNum = target - nums[i]
            if (nums.indexOf(neededNum) !== -1 && nums.indexOf(neededNum) !== i) result = [nums[i], nums[nums.indexOf(neededNum)]]
        }
        
        res.status(200).json({ result: result })
        
    } catch (err) {
        res.status(500).send(err)
    }
})

/* Export server object */
module.exports = server

/* Initialize server */
server.listen(3001, () => console.log('Server is listening.') )
server.on('error', error => console.error(error) )

ご覧のとおり、エンドポイント1はグリーティングメッセージを返すだけです。エンドポイント2と3は、バニラJSの例で見た機能を応用したものです。これで、リクエスト内のパラメータを受け取り、戻り値がレスポンスに送られます。😉

今テスト!ファイルを作成し、そのapp.test.js中にこのコードを配置します。

// app.test.js
const supertest = require('supertest') // Import supertest
const server = require("./app") // Import the server object
const requestWithSupertest = supertest(server) // We will use this function to mock HTTP requests

afterEach(done => { // afterEach function is provided by Jest and executes once all tests are finished
    server.close() // We close the server connection once all tests have finished
    done()
})

test('GET "/" returns greeting', async () => {
    const res = await requestWithSupertest.get('/')
    expect(res.status).toEqual(200)
    expect(res.type).toEqual(expect.stringContaining('json'))
    expect(res.body).toEqual({ greeting: "Hello there!" })
})

describe("/isPalindrome", () => {
    test('GET "/isPalindrome" neuquen returns true', async () => {
        const res = await requestWithSupertest.get('/isPalindrome').set('Content-type', 'application/json').send({ "string":"neuquen" })
        expect(res.status).toEqual(200)
        expect(res.type).toEqual(expect.stringContaining('json'))
        expect(res.body).toEqual({ result: true })
    })

    test('GET "/isPalindrome" bariloche returns true', async () => {
        const res = await requestWithSupertest.get('/isPalindrome').set('Content-type', 'application/json').send({ "string":"bariloche" })
        expect(res.status).toEqual(200)
        expect(res.type).toEqual(expect.stringContaining('json'))
        expect(res.body).toEqual({ result: false })
    })
})

describe("/twoSum", () => {
    test('GET "/twoSum" [2,7,11,15] and 9 returns [7, 2]', async () => {
        const res = await requestWithSupertest.get('/twoSum').set('Content-type', 'application/json').send({ "nums":"[2,7,11,15]", "target": "9" })
        expect(res.status).toEqual(200)
        expect(res.type).toEqual(expect.stringContaining('json'))
        expect(res.body).toEqual({ result: [7, 2] })
    })

    test('GET "/twoSum" [3,2,4] and 6 returns [4, 2]', async () => {
        const res = await requestWithSupertest.get('/twoSum').set('Content-type', 'application/json').send({ "nums":"[3,2,4]", "target": "6" })
        expect(res.status).toEqual(200)
        expect(res.type).toEqual(expect.stringContaining('json'))
        expect(res.body).toEqual({ result: [4, 2] })
    })

    test('GET "/twoSum" [3,2,4] and 10 returns false', async () => {
        const res = await requestWithSupertest.get('/twoSum').set('Content-type', 'application/json').send({ "nums":"[3,2,4]", "target": "10" })
        expect(res.status).toEqual(200)
        expect(res.type).toEqual(expect.stringContaining('json'))
        expect(res.body).toEqual({ result: false })
    })
})

私たちがしていることを分析しましょう:

  • リクエストをモックしますrequestWithSupertest.get('/')
  • 次に、resオブジェクトを分割して「分割」し、その各部分をアサートします。
    • 応答ステータスを確認します。expect(res.status).toEqual(200)
    • 応答形式を確認してください。expect(res.type).toEqual(expect.stringContaining('json'))
    • 応答本文の内容を確認してください。expect(res.body).toEqual({ greeting: "Hello there!" })

他のテストは、次のようにモックリクエスト本文でデータを送信することを除いて、実際に似ています。

const res = await requestWithSupertest.get('/isPalindrome').set('Content-type', 'application/json').send({ "string":"bariloche" })

ご覧のとおり、Jestに慣れれば、この方法でのテストは非常に簡単です。HTTPリクエストをモックするために、Supertestによる少しの助けが必要であり、残りは応答をアサートするだけです。👏👏

でテストを実行npm testでき、次の応答が得られるはずです。

// console
 PASS  ./app.test.js
  ✓ GET "/" returns greeting (46 ms)
  /isPalindrome
    ✓ GET "/isPalindrome" neuquen returns true (18 ms)
    ✓ GET "/isPalindrome" bariloche returns true (3 ms)
  /twoSum
    ✓ GET "/twoSum" [2,7,11,15] and 9 returns [7, 2] (4 ms)
    ✓ GET "/twoSum" [3,2,4] and 6 returns [4, 2] (3 ms)
    ✓ GET "/twoSum" [3,2,4] and 10 returns false (2 ms)

Test Suites: 1 passed, 1 total
Tests:       6 passed, 6 total
Snapshots:   0 total
Time:        0.552 s, estimated 1 s
Ran all test suites.

要約

以上です!JSアプリのフロントエンドとバックエンドの両方をテストできる4つの非常に人気のあるツールの基本について説明しました。

もちろん、これまでに見たすべてのツールや、まだ取り上げていない多くの機能には、さらに多くの機能があります。しかし、アイデアは、テストの世界で最初の一歩を踏み出すことができるように、あなたに紹介を与えることでした。

いつものように、あなたが記事を楽しんで、何か新しいことを学んだことを願っています。 

乾杯、次でお会いしましょう!= D

さようなら-さようなら--1- 

ソース:https ://www.freecodecamp.org/news/test-a-react-app-with-jest-testing-library-and-cypress/ 

#jest #testing #cypress #react 

Tamia  Walter

Tamia Walter

1596754901

Testing Microservices Applications

The shift towards microservices and modular applications makes testing more important and more challenging at the same time. You have to make sure that the microservices running in containers perform well and as intended, but you can no longer rely on conventional testing strategies to get the job done.

This is where new testing approaches are needed. Testing your microservices applications require the right approach, a suitable set of tools, and immense attention to details. This article will guide you through the process of testing your microservices and talk about the challenges you will have to overcome along the way. Let’s get started, shall we?

A Brave New World

Traditionally, testing a monolith application meant configuring a test environment and setting up all of the application components in a way that matched the production environment. It took time to set up the testing environment, and there were a lot of complexities around the process.

Testing also requires the application to run in full. It is not possible to test monolith apps on a per-component basis, mainly because there is usually a base code that ties everything together, and the app is designed to run as a complete app to work properly.

Microservices running in containers offer one particular advantage: universal compatibility. You don’t have to match the testing environment with the deployment architecture exactly, and you can get away with testing individual components rather than the full app in some situations.

Of course, you will have to embrace the new cloud-native approach across the pipeline. Rather than creating critical dependencies between microservices, you need to treat each one as a semi-independent module.

The only monolith or centralized portion of the application is the database, but this too is an easy challenge to overcome. As long as you have a persistent database running on your test environment, you can perform tests at any time.

Keep in mind that there are additional things to focus on when testing microservices.

  • Microservices rely on network communications to talk to each other, so network reliability and requirements must be part of the testing.
  • Automation and infrastructure elements are now added as codes, and you have to make sure that they also run properly when microservices are pushed through the pipeline
  • While containerization is universal, you still have to pay attention to specific dependencies and create a testing strategy that allows for those dependencies to be included

Test containers are the method of choice for many developers. Unlike monolith apps, which lets you use stubs and mocks for testing, microservices need to be tested in test containers. Many CI/CD pipelines actually integrate production microservices as part of the testing process.

Contract Testing as an Approach

As mentioned before, there are many ways to test microservices effectively, but the one approach that developers now use reliably is contract testing. Loosely coupled microservices can be tested in an effective and efficient way using contract testing, mainly because this testing approach focuses on contracts; in other words, it focuses on how components or microservices communicate with each other.

Syntax and semantics construct how components communicate with each other. By defining syntax and semantics in a standardized way and testing microservices based on their ability to generate the right message formats and meet behavioral expectations, you can rest assured knowing that the microservices will behave as intended when deployed.

Ways to Test Microservices

It is easy to fall into the trap of making testing microservices complicated, but there are ways to avoid this problem. Testing microservices doesn’t have to be complicated at all when you have the right strategy in place.

There are several ways to test microservices too, including:

  • Unit testing: Which allows developers to test microservices in a granular way. It doesn’t limit testing to individual microservices, but rather allows developers to take a more granular approach such as testing individual features or runtimes.
  • Integration testing: Which handles the testing of microservices in an interactive way. Microservices still need to work with each other when they are deployed, and integration testing is a key process in making sure that they do.
  • End-to-end testing: Which⁠—as the name suggests⁠—tests microservices as a complete app. This type of testing enables the testing of features, UI, communications, and other components that construct the app.

What’s important to note is the fact that these testing approaches allow for asynchronous testing. After all, asynchronous development is what makes developing microservices very appealing in the first place. By allowing for asynchronous testing, you can also make sure that components or microservices can be updated independently to one another.

#blog #microservices #testing #caylent #contract testing #end-to-end testing #hoverfly #integration testing #microservices #microservices architecture #pact #testing #unit testing #vagrant #vcr