👨🏼‍💻

khriztianmoreno's Blog

Inicio Etiquetas Acerca |

Posts with tag testing

Cómo mockear una solicitud HTTP con Jest 💻

2024-05-07
javascripttestingnodejsjestweb-development

Hoy quería mostrarles cómo escribir correctamente una prueba.Pero cualquiera puede encontrar cómo ejecutar una prueba sencilla. Y aquí, buscamos ayudarle a encontrar respuestas que no encontrará en ningún otro lugar.Entonces pensé que llevaríamos las cosas un paso más allá.Ejecutemos una prueba más compleja, en la que tendrás que simular 1 o 2 partes de la función que estás probando.[En caso de que seas nuevo aquí: mock es como usar un doble en una película. Es una forma de reemplazar una parte complicada de tu código (como llamar a una API) con algo más simple que pretende ser real, para que pueda probar el resto de tu código fácilmente].MI testing framework elegido es Jest, porque hace que todo sea mucho más fácil:Configuración cero: una de las principales ventajas de Jest es su configuración sin configuración. Está diseñado para funcionar desde el primer momento con una configuración mínima, lo que lo hace muy atractivo para proyectos que desean implementar pruebas de manera rápida y eficiente.Prueba de instantáneas: Jest introdujo el concepto de Snapshot Testing, que es particularmente útil para probar componentes de la interfaz de usuario. Toma una instantánea de la salida renderizada de un componente y garantiza que no cambie inesperadamente en pruebas futuras.Mocking y Spies Integrados: Jest viene con soporte integrado para funciones, módulos y temporizadores simulados, lo que facilita la prueba de componentes o funciones de forma aislada sin preocuparse por sus dependencias.Compatibilidad con pruebas asincrónicas: Jest admite pruebas asincrónicas listas para usar, lo cual es esencial para las pruebas en aplicaciones JavaScript modernas que a menudo dependen de operaciones asincrónicas como llamadas API o consultas de bases de datos.Image descriptionDe todos modos, entremos en las pruebas:Paso 1: configurar tu proyectoCree un nuevo directorio de proyecto y navegue hasta élInicialice un nuevo proyecto npm: npm init -yInstale Jest: npm install --save-dev jestInstale axios para realizar solicitudes HTTP: npm install axiosEstos son los requisitos básicos. Nada nuevo o sofisticado aquí. Vamonos.Paso 2: escribir una función con una llamada APIAhora, digamos que inicia sesión en algún tipo de aplicación. StackOverflow, por ejemplo. Lo más probable es que en la parte superior derecha veas información sobre tu perfil. Tal vez su nombre completo y nombre de usuario, por ejemplo.Para obtenerlos, normalmente tenemos que realizar una llamada a la API para obtenerlos. Entonces, veamos cómo haríamos eso.Cree un archivo llamado user.jsDentro de user.js, escriba una función que realice una llamada API. Por ejemplo, usar axios para recuperar datos del usuario:// user.js import axios from "axios"; export const getUser = async (userId) => { const response = await axios.get(`https://api.example.com/users/${userId}`); return response.data; };Paso 3: crear el archivo de pruebaBien, ahora que tenemos una función que nos trae el usuario según la identificación que solicitamos, veamos cómo podemos probarla.Recuerde, queremos algo que funcione siempre y para todos los desarrolladores.Lo que significa que no queremos depender de si el servidor se está ejecutando o no (ya que esto no es lo que estamos probando).Y no queremos depender de los usuarios que tenemos en la base de datos.Porque en mi base de datos, el ID1 podría pertenecer a mi usuario administrador, mientras que en su base de datos, el ID1 podría pertenecer a SU usuario administrador.Esto significa que la misma función nos daría resultados diferentes. Lo que haría que la prueba fallara, aunque la función funcione correctamente.Siga leyendo para ver cómo abordamos este problema mediante los mocks.Cree un archivo llamado user.test.js en el mismo directorio.Dentro de este archivo, importe la función que desea probar:import axios from "axios"; jest.mock("axios"); import { getUser } from "./user";Escriba su caso de prueba, simule la llamada y recupere datos simulados.test("should fetch user data", async () => { // Mock data to be returned by the Axios request const mockUserData = { id: "1", name: "John Doe" }; axios.get.mockResolvedValue({ data: mockUserData }); // Call the function const result = await getUser("1"); // Assert that the Axios get method was called correctly expect(axios.get).toHaveBeenCalledWith("https://api.example.com/users/1"); // Assert that the function returned the correct data expect(result).toEqual(mockUserData); });Paso 4: ejecutar la pruebaAgregue un script de prueba a su package.json:"scripts": { "test": "jest" }Ejecute sus pruebas con npm test.Paso 5: revise los resultadosJest mostrará el resultado de su prueba en la terminal. La prueba debería pasar, lo que indica que getUser está devolviendo los datos simulados como se esperaba.Felicitaciones, ahora tienes una prueba funcional con Jest y Mocking.¡Espero que esto haya sido útil y/o te haya hecho aprender algo nuevo!Profile@khriztianmoren

¿Estás cometiendo ESTOS errores de pruebas y mocking unitarios?

2024-04-08
javascripttestingweb-development

Las pruebas son difíciles.Y no importa si eres un tester experimentado o principiante...Si ha realizado un esfuerzo significativo para probar una aplicación...Es probable que hayas cometido algunos de estos errores de prueba y mocking en el pasado.Desde casos de prueba repletos de código duplicado y enormes hooks de ciclo de vida, hasta casos de mocking convenientemente incorrectos y casos extremos que faltan y furtivos, hay muchos culpables comunes.He seguido algunos de los casos más populares y los enumero a continuación. Continúe y cuente cuántos de ellos ha hecho en el pasado.Con suerte, será una buena ronda.¿Por qué la gente comete errores en las pruebas en primer lugar?Si bien las pruebas automatizadas son una de las partes más importantes del proceso de desarrollo...Y las pruebas unitarias nos ahorran innumerables horas de pruebas manuales e innumerables errores que quedan atrapados en los conjuntos de pruebas...Muchas empresas no utilizan pruebas unitarias o no ejecutan suficientes pruebas.¿Sabía que la cobertura de prueba promedio de un proyecto es de ~40%, mientras que la recomendada es del 80%?Image descriptionEsto significa que mucha gente no está acostumbrada a ejecutar pruebas (especialmente casos de prueba complejos) y cuando no estás acostumbrado a hacer algo, eres más propenso a cometer un error.Entonces, sin más preámbulos, veamos algunos de los errores de prueba más comunes que veoCódigo duplicadoLas tres reglas más importantes del desarrollo de software son también las tres reglas más importantes de las pruebas.¿Cuáles son estas reglas? Reutilizar. Reutilizar. Reutilizar.Un problema común que veo es repetir la misma serie de comandos en cada prueba en lugar de moverlos a un enlace de ciclo de vida como beforeEach o afterEachEsto podría deberse a que el desarrollador estaba creando un prototipo o a que el proyecto era pequeño y el cambio insignificante. Estos casos son buenos y aceptables.Pero unos cuantos casos de prueba más tarde, el problema de la duplicación de código se vuelve cada vez más evidente.Y aunque esto es más bien un error de un desarrollador junior, el siguiente es similar pero mucho más astuto.Sobrecargar los hooks del ciclo de vidaEn la otra cara de la misma moneda, a veces estamos demasiado ansiosos por refactorizar nuestros casos de prueba y ponemos tantas cosas en los hooks del ciclo de vida sin pensarlo dos veces que no vemos el problema que nos estamos creando.A veces, los hooks del ciclo de vida crecen demasiado.Y cuando esto sucede......y necesitas desplazarte hacia arriba y hacia abajo para ir desde el hook al caso de prueba y viceversa...Esto es un problema y a menudo se lo denomina "fatiga de desplazamiento".Recuerdo haber sido culpable de esto en el pasado.Un patrón/práctica común para mantener el archivo legible cuando tenemos hooks de ciclo de vida inflados es extraer el código de configuración común en pequeñas funciones de fábrica.Entonces, imaginemos que tenemos algunas (docenas de) casos de prueba que se ven así:describe("authController", () => { describe("signup", () => { test("given user object, returns response with 201 status", async () => { // Arrange const userObject = { // several lines of user setup code }; const dbUser = { // several lines of user setup code }; mockingoose(User).toReturn(undefined, "findOne"); mockingoose(User).toReturn(dbUser, "save"); const mockRequest = { // several lines of constructing the request }; const mockResponse = { // several lines of constructing the response }; // Act await signup(mockRequest, mockResponse); // Assert expect(mockResponse.status).toHaveBeenCalled(); expect(mockResponse.status).toHaveBeenCalledWith(201); }); test("given user object with email of an existing user, returns 400 status - 1", async () => { // Arrange const userObject = { // several lines of user setup code }; const dbUser = { // several lines of user setup code }; const mockRequest = { // several lines of constructing the request }; const mockJson = jest.fn(); const mockResponse = { // several lines of constructing the response }; mockingoose(User).toReturn(dbUser, "findOne"); // Act await signup(mockRequest, mockResponse); // Assert expect(mockResponse.status).toHaveBeenCalled(); expect(mockResponse.status).toHaveBeenCalledWith(400); expect(mockJson).toHaveBeenCalled(); expect(mockJson).toHaveBeenCalledWith({ status: "fail", message: "Email taken.", }); }); }); });Podemos extraer la información de configuración repetida en sus propias funciones llamadas createUserObject, createDbUserObject y createMocksY luego las pruebas quedarían así:test("given user object, returns response with 201 status", async () => { const userObject = createUserObject(); const dbUser = createDbUserObject(); const [mockRequest, mockResponse] = createMocks(userObject); mockingoose(User).toReturn(undefined, "findOne"); mockingoose(User).toReturn(dbUser, "save"); await signup(mockRequest, mockResponse); expect(mockResponse.status).toHaveBeenCalled(); expect(mockResponse.status).toHaveBeenCalledWith(201); });Al extraer esos fragmentos de código en sus propias funciones de fábrica separadas, podemos evitar la fatiga del desplazamiento, mantener los enlaces del ciclo de vida ágiles y facilitar la navegación por el archivo y encontrar lo que estamos buscando.No priorizar los tipos de pruebas que ejecutasEsto tiene más que ver con bases de código grandes o enormes donde hay literalmente cientos o incluso miles de casos de prueba ejecutándose cada vez que una nueva serie de commits quiere fusionarse en la base de código.Image descriptionEn tales casos, ejecutar todos los conjuntos de pruebas puede llevar literalmente horas y es posible que no siempre tenga el tiempo o los recursos para hacerlo.Cuando el tiempo o los recursos están limitados, es importante elegir estratégicamente el tipo de prueba a priorizar. Generalmente, las pruebas de integración brindan mejores garantías de confiabilidad debido a su alcance más amplio. Por lo tanto, cuando se tiene que elegir entre los dos, suele ser una buena idea elegir las pruebas de integración en lugar de las pruebas unitarias.Image descriptionUsar lógica en tus casos de pruebaQueremos evitar la lógica en nuestros casos de prueba siempre que sea posible.Los casos de prueba solo deben tener una validación simple y evitar cosas como bloques try-catch o condicionales if-else.Esto mantiene tus pruebas limpias y enfocadas solo en el flujo esperado porque hace que las pruebas sean más fáciles de entender de un vistazo.La única excepción es cuando estás escribiendo funciones auxiliares o de fábrica que configuran escenarios para pruebas.Utilizar validaciones vagas en lugar de afirmaciones estrictasEsto suele ser una señal de que es posible que necesites refactorizar el fragmento de código que estás probando o que necesites hacer un ajuste menor en tus mocks.Por ejemplo, en lugar de comprobar si el valor es mayor que 1, deberías ser más específico y afirmar que el valor es 2.O, si está verificando los datos de un objeto Usuario, debe afirmar que cada dato es exactamente como lo espera, en lugar de simplemente verificar una coincidencia de ID.Los controles sueltos pueden enmascarar casos extremos que podrían fallar en el futuro.Implementación incorrecta del Mock BehaviorEste es difícil de encontrar y es por eso que puedes encontrar un ejemplo en cada código base.Es uno de los problemas de prueba más astutos pero comunes y es difícil notarlo a primera vista.Puede suceder cuando el comportamiento del mock está demasiado simplificado o cuando no refleja con precisión los casos extremos y las condiciones de error.Como resultado, las pruebas pueden pasar, pero no proporcionarán una indicación confiable de cómo funcionará el sistema bajo diversas condiciones, lo que resulta en errores futuros y problemas inesperados, y casos de prueba con comportamiento simulado que terminan haciendo más daño que bien.Espero este post te ayude a indetificar esas practicas que deberiamos evitar al momento de hacer pruebas.Profile@khriztianmoren

Testing framework - Node.js

2020-04-17
javascripttestingnodejs

Una vez que una aplicación se está ejecutando en producción, puede darnos miedo hacer cambios. ¿Cómo sabemos que una nueo feature, un fix o un refactor no romperá la funcionalidad existente?Podemos usar nuestra aplicación manualmente para tratar de encontrar errores, pero sin mantener una lista de verificación exhaustiva, es poco probable que cubramos todos los posibles puntos de falla. Y, sinceramente, incluso si lo hiciéramos, llevaría demasiado tiempo ejecutar nuestra aplicación completa después de cada commit.Al usar un framework de testing, podemos escribir código que verifique que nuestro código anterior aún funciona. Esto nos permite realizar cambios sin temor a romper la funcionalidad esperada.Pero hay muchos frameworks de testing diferentes, puede ser difícil saber cuál usar. A continuación, voy a hablar sobre tres de ellos para Node.js:TapeAvaJestTAPEEste deriva su nombre de su capacidad para proporcionar resultados estructurados a través de TAP (Test Anything Protocol). La salida de nuestro runner es amigable para los humanos, pero otros programas y aplicaciones no la pueden analizar fácilmente. El uso de un protocolo estándar permite una mejor interoperabilidad con otros sistemas.Además, Tape tiene varios métodos de conveniencia que nos permiten omitir y aislar pruebas específicas, así como verificar expectativas adicionales como errores, deep equality y throwing.En general, la ventaja de Tape es su simplicidad y velocidad. Es un arnés sólido y sencillo que hace el trabajo sin una curva de aprendizaje empinada.Así es como se ve una prueba básica con tape:const test = require("tape"); test("timing test", (t) => { t.plan(2); t.equal(typeof Date.now, "function"); const start = Date.now(); setTimeout(function () { t.equal(Date.now() - start, 100); }, 100); });Y si lo ejecutamos, se ve así:$ node example/timing.js TAP version 13 # timing test ok 1 should be strictly equal not ok 2 should be strictly equal --- operator: equal expected: 100 actual: 107 ... 1..2 # tests 2 # pass 1 # fail 1El método test() espera dos argumentos: el nombre de la prueba y la función de prueba. La función de prueba tiene el objeto t como argumento, y este objeto tiene métodos que podemos usar para aserciones: t.ok(), t.notOk(), t.equal() y t.deepEqual() solo para nombrar un pocos.AVAAVA tiene una API concisa, salida de error detallada, abarca nuevas características de lenguaje y tiene aislamiento de proceso para ejecutar pruebas en paralelo. AVA está inspirado en la sintaxis de Tape y admite la generación de informes a través de TAP, pero se desarrolló para ser más obstinado, proporcionar más funciones y poder ejecutar pruebas al mismo tiempo.AVA solo ejecutará pruebas ava binary. Con Tape podríamos ejecutar node my-tape-test.js, pero con AVA primero debemos asegurarnos de que: AVA esté instalado globalmente y disponible en la línea de comandos (por ejemplo, npm i -g ava).Además, AVA es exigente acerca de cómo se nombran los archivos de prueba y no se ejecutará a menos que el archivo termine con "test.js".Una cosa que debe saber sobre AVA es que por defecto ejecuta pruebas en paralelo. Esto puede acelerar muchas pruebas, pero no es ideal en todas las situaciones. Cuando las pruebas que leen y escriben en la base de datos se ejecutan simultáneamente, pueden afectarse entre sí.AVA también tiene algunas funciones útiles de ayuda que facilitan la configuración y el desmontaje: métodos test.before() y test.after() para la configuración y limpieza.AVA también tiene métodos test.beforeEach() y test.afterEach() que se ejecutan antes o después de cada prueba. Si tuviéramos que agregar más pruebas de base de datos, podríamos borrar nuestra base de datos aquí en lugar de pruebas individuales.Así es como se ve una prueba de AVA:const test = require("ava"); test("foo", (t) => { t.pass(); }); test("bar", async (t) => { const bar = Promise.resolve("bar"); t.is(await bar, "bar"); });Al iterar en las pruebas, puede ser útil ejecutar AVA en "watch mode". Esto observará tus archivos en busca de cambios y volverá a ejecutar automáticamente las pruebas. Esto funciona particularmente bien cuando creamos primero una prueba fallida. Podemos concentrarnos en agregar funcionalidad sin tener que seguir cambiando para reiniciar las pruebas.AVA es muy popular y es fácil ver por qué. AVA es una excelente opción si estamos buscando algo que nos facilite la ejecución simultánea de pruebas, proporcione helpers como before() y afterEach() y proporcione un mejor rendimiento por defecto, todo mientras mantiene una API concisa y fácil de entender.JestEs un framework de pruebas que ha aumentado en popularidad junto con React.js. La documentación de React lo enumeran como la forma recomendada de probar React, ya que permite usar jsdom para simular fácilmente un entorno de navegador. También proporciona funciones para ayudar a simular módulos y temporizadores.Aunque Jest es muy popular, se usa principalmente para pruebas de front-end. Utiliza Node.js para ejecutarse, por lo que es capaz de probar tanto el código basado en el navegador como las aplicaciones y módulos de Node.js. Sin embargo, tenga en cuenta que el uso de Jest para probar las aplicaciones del lado del servidor Node.js viene con advertencias y configuración adicional.En general, Jest tiene muchas funcionalidades que pueden ser atractivas. Aquí hay algunas diferencias clave de Tape y AVA:Jest no se comporta como un módulo Node.js normal.El archivo de prueba debe ejecutarse con jest, y varias funciones se agregan automáticamente al alcance global (por ejemplo, describe(), test(), beforeAll() y expect()). Esto hace que los archivos de prueba sean "especiales" ya que no siguen la convención de Node.js de usar require() para cargar la funcionalidad de jest. Esto causará problemas con linters como standard que restringen el uso de globales indefinidos.Jest utiliza su expect() global para realizar comprobaciones, en lugar de afirmaciones estándar. Jest espera que se lea más como inglés. Por ejemplo, en lugar de hacer algo como t.equal(actual, expected, comment) con tape y AVA, usamos expect(actual).toBe(expected). Jest también tiene modificadores inteligentes que puede incluir en la cadena como .not() (por ejemplo, expect(actual).not.toBe(unexpected)).Jest tiene la capacidad de mockear funciones y módulos. Esto puede ser útil en situaciones en las que es difícil escribir o cambiar el código que estamos probando para evitar resultados lentos o impredecibles en un entorno de prueba. Un ejemplo en la documentación de Jest es evitar que axios realice una solicitud HTTP real a un servidor externo y, en su lugar, devolver una respuesta preconfigurada.Jest tiene una API mucho más grande y con muchas más opciones de configuración. Algunos de ellos no funcionan bien cuando se realizan pruebas para Node.js. La opción más importante que debemos establecer es que testEnvironment debe ser "node". Si no hacemos esto, jest usa la configuración predeterminada en la que nuestras pruebas se ejecutarán en un entorno similar a un navegador usando jsdom.Así es como se ve una prueba de Jest:const sum = require("./sum"); test("adds 1 + 2 to equal 3", () => { expect(sum(1, 2)).toBe(3); });Jest tiene una API mucho más grande y ofrece más funcionalidad que AVA o tape. Sin embargo, el mayor alcance no está exento de inconvenientes. Al usar Jest para probar el código Node.js, tenemos que:Estar de acuerdo con el uso de globales indefinidos.No usar funciones como temporizadores simulados que interfieren con paquetes como Mongoose.Debemos configurar el entorno correctamente para que no se ejecute en un navegador simulado de forma predeterminada.Considere que cierto código puede correr 20-30 veces más lento en Jest en comparación con otros test runners.Muchos equipos elegirán Jest porque ya lo están utilizando en el front-end y no les gusta la idea de tener múltiples test runners, o les gustan las características integradas como mocks y no quieren incorporar módulos adicionales. En última instancia, estas compensaciones deben realizarse caso por caso.Otras herramientas de pruebaHay un montón de otras herramientas de prueba como Istanbul, nyc, nock y replay que no tenemos espacio para entrar aquí.¡Espero que esto haya sido útil y/o te haya hecho aprender algo nuevo!Profile@khriztianmoreno �