Logotipo de Zephyrnet

Mirage JS Deep Dive: uso de Mirage JS y Cypress para pruebas de IU (Parte 4)

Fecha:

Sobre el Autor

Kelvin Omereshone es el CTO en Laboratorio de Quru. Kelvin fue anteriormente un ingeniero de front-end en myPadi.ng. Es el creador de la comunidad de Nuxtjs África y muy apasionado ...
Más información sobre
Kelvin
...

En esta parte final de la serie Mirage JS Deep Dive, pondremos todo lo que hemos aprendido en la serie anterior para aprender a realizar pruebas de IU con Mirage JS.

Una de mis citas favoritas sobre las pruebas de software es de la documentación de Flutter. Dice:

“¿Cómo puede asegurarse de que su aplicación siga funcionando a medida que agrega más funciones o cambia la funcionalidad existente? Escribiendo pruebas ".

En esa nota, esta última parte del Serie Mirage JS Deep Dive se centrará en el uso de Mirage para probar su aplicación front-end de JavaScript.

Note: Este artículo asume una Cypress ambiente. Cypress es un marco de prueba para pruebas de interfaz de usuario. Sin embargo, puede transferir el conocimiento aquí a cualquier entorno o marco de prueba de IU que utilice.

Leer Partes anteriores De la serie:

  • Parte 1: Comprensión de los modelos y asociaciones de Mirage JS
  • Parte 2: Comprensión de fábricas, accesorios y serializadores
  • Parte 3: Comprender el tiempo, la respuesta y el traspaso

Introducción a las pruebas de IU

La prueba de IU o interfaz de usuario es una forma de test de aceptación hecho para verificar el usuario flujos de su aplicación front-end. El énfasis de este tipo de pruebas de software está en el usuario final, que es la persona real que interactuará con su aplicación web en una variedad de dispositivos que van desde computadoras de escritorio y portátiles hasta dispositivos móviles. Estas usuarios sería una interfaz o interacción con su aplicación utilizando dispositivos de entrada como un teclado, un mouse o pantallas táctiles. Las pruebas de IU, por lo tanto, están escritas para imitar el usuario interacción con su aplicación lo más cercana posible.

Tomemos un sitio web de comercio electrónico, por ejemplo. Un escenario típico de prueba de IU sería:

  • El usuario puede ver la lista de productos cuando visita la página de inicio.

Otros escenarios de prueba de IU pueden ser:

  • El usuario puede ver el nombre de un producto en la página de detalles del producto.
  • El usuario puede hacer clic en el botón "agregar al carrito".
  • El usuario puede pagar.

Captas la idea ¿cierto?

Al realizar pruebas de interfaz de usuario, se basará principalmente en los estados de back-end, es decir, ¿devolvió los productos o hubo un error? El papel que juega Mirage en esto es hacer que esos estados del servidor estén disponibles para que los modifique según lo necesite. Entonces, en lugar de realizar una solicitud real a su servidor de producción en sus pruebas de interfaz de usuario, realiza la solicitud al servidor simulado de Mirage.

Para la parte restante de este artículo, realizaremos pruebas de IU en una IU de aplicación web de comercio electrónico ficticia. Entonces empecemos.

Nuestra primera prueba de UI

Como se indicó anteriormente, este artículo asume un entorno Cypress. Cypress hace que probar la interfaz de usuario en la web sea rápido y fácil. Puede simular clics y navegación y puede visitar rutas mediante programación en su aplicación. Ver el documentos para más información sobre Cypress.

Entonces, asumiendo que Cypress y Mirage están disponibles para nosotros, comencemos definiendo una función de proxy para su solicitud de API. Podemos hacerlo en el support/index.js archivo de nuestra configuración de Cypress. Simplemente pegue el siguiente código en:

// cypress/support/index.js
Cypress.on("window:before:load", (win) => { win.handleFromCypress = function (request) { return fetch(request.url, { method: request.method, headers: request.requestHeaders, body: request.requestBody, }).then((res) => { let content = res.headers.map["content-type"] === "application/json" ? res.json() : res.text() return new Promise((resolve) => { content.then((body) => resolve([res.status, res.headers, body])) }) }) }
})

Luego, en el archivo de arranque de su aplicación (main.js para Vue, index.js para React), usaremos Mirage para enviar las solicitudes de API de su aplicación al handleFromCypress funcionan solo cuando Cypress se está ejecutando. Aquí está el código para eso:

import { Server, Response } from "miragejs" if (window.Cypress) { new Server({ environment: "test", routes() { let methods = ["get", "put", "patch", "post", "delete"] methods.forEach((method) => { this[method]("/*", async (schema, request) => { let [status, headers, body] = await window.handleFromCypress(request) return new Response(status, headers, body) }) }) }, })
}

Con esa configuración, cada vez que se ejecuta Cypress, su aplicación sabe usar Mirage como el servidor simulado para todas las solicitudes de API.

Sigamos escribiendo algunas pruebas de IU. Comenzaremos probando nuestra página de inicio para ver si tiene 5 productos desplegado. Para hacer esto en Cypress, necesitamos crear un homepage.test.js presentar en el tests carpeta en la raíz del directorio de su proyecto. A continuación, le diremos a Cypress que haga lo siguiente:

  • Visite la página de inicio, es decir / ruta
  • Entonces afirmar si tiene elementos li con la clase de product y también comprueba si son 5 en números.

Aquí está el código:

// homepage.test.js
it('shows the products', () => { cy.visit('/'); cy.get('li.product').should('have.length', 5);
});

Es posible que haya adivinado que esta prueba fallaría porque no tenemos un servidor de producción que devuelva 5 productos a nuestra aplicación de front-end. ¿Asi que que hacemos? ¡Nos burlamos del servidor en Mirage! Si traemos Mirage, puede interceptar todas las llamadas de red en nuestras pruebas. Hagamos esto a continuación e iniciemos el servidor Mirage antes de cada prueba en el beforeEach función y también apagarlo en el afterEach función. los beforeEach y afterEach Las funciones son proporcionadas por Cypress y se pusieron a disposición para que pueda ejecutar el código antes y después de cada ejecución de prueba en su conjunto de pruebas, de ahí el nombre. Así que veamos el código para esto:

// homepage.test.js
import { Server } from "miragejs" let server beforeEach(() => { server = new Server()
}) afterEach(() => { server.shutdown()
}) it("shows the products", function () { cy.visit("/") cy.get("li.product").should("have.length", 5)
})

Bien, estamos llegando a alguna parte; hemos importado el servidor de Mirage y lo estamos iniciando y apagando en beforeEach y afterEach funciones respectivamente. Vamos a burlarnos de nuestro recurso de productos.


// homepage.test.js
import { Server, Model } from 'miragejs'; let server; beforeEach(() => { server = new Server({ models: { product: Model, }, routes() { this.namespace = 'api'; this.get('products', ({ products }, request) => { return products.all(); }); }, });
}); afterEach(() => { server.shutdown();
}); it('shows the products', function() { cy.visit('/'); cy.get('li.product').should('have.length', 5);
});

Note: Siempre puede echar un vistazo a las partes anteriores de esta serie si no comprende los bits de Mirage del fragmento de código anterior.

  • Parte 1: Descripción de los modelos y asociaciones de Mirage JS
  • Parte 2: Comprender las fábricas, los accesorios y los serializadores
  • Parte 3: Comprender el tiempo, la respuesta y el paso a través

Bien, hemos comenzado a desarrollar nuestra instancia de servidor creando el modelo de producto y también creando el controlador de ruta para el /api/products ruta. Sin embargo, si ejecutamos nuestras pruebas, fallará porque aún no tenemos ningún producto en la base de datos de Mirage.

Completemos la base de datos de Mirage con algunos productos. Para hacer esto, podríamos haber usado el create() en nuestra instancia de servidor, pero crear 5 productos a mano parece bastante tedioso. Debería haber una mejor manera.

Ah, sí, lo hay. Utilicemos fábricas (como se explica en el segunda parte de esta serie). Necesitaremos crear nuestra fábrica de productos así:

// homepage.test.js
import { Server, Model, Factory } from 'miragejs'; let server; beforeEach(() => { server = new Server({ models: { product: Model, }, factories: { product: Factory.extend({ name(i) { return `Product ${i}` } }) }, routes() { this.namespace = 'api'; this.get('products', ({ products }, request) => { return products.all(); }); }, });
}); afterEach(() => { server.shutdown();
}); it('shows the products', function() { cy.visit('/'); cy.get('li.product').should('have.length', 5);
});

Entonces, finalmente, usaremos createList() para crear rápidamente los 5 productos que nuestra prueba debe pasar.

Hagámoslo:

// homepage.test.js
import { Server, Model, Factory } from 'miragejs'; let server; beforeEach(() => { server = new Server({ models: { product: Model, }, factories: { product: Factory.extend({ name(i) { return `Product ${i}` } }) }, routes() { this.namespace = 'api'; this.get('products', ({ products }, request) => { return products.all(); }); }, });
}); afterEach(() => { server.shutdown();
}); it('shows the products', function() { server.createList("product", 5) cy.visit('/'); cy.get('li.product').should('have.length', 5);
});

Entonces, cuando ejecutamos nuestra prueba, ¡pasa!

Note: Después de cada prueba, el servidor de Mirage se apaga y se reinicia, por lo que nada de este estado se filtrará en las pruebas.

Si ha estado siguiendo esta serie, notará cuando estábamos usando Mirage en desarrollo para interceptar nuestras solicitudes de red; teniamos server.js archivo en la raíz de nuestra aplicación donde configuramos Mirage. En el espíritu de DRY (Don't Repeat Yourself), creo que sería bueno utilizar esa instancia de servidor en lugar de tener dos instancias separadas de Mirage tanto para el desarrollo como para las pruebas. Para hacer esto (en caso de que no tenga un server.js archivo ya), simplemente cree uno en su proyecto src directorio.

Note: Su estructura será diferente si está utilizando un marco de JavaScript, pero la idea general es configurar el archivo server.js en la raíz src de su proyecto.

Entonces, con esta nueva estructura, exportaremos una función en server.js que es responsable de crear nuestra instancia de servidor Mirage. Vamos a hacer eso:

// src/server.js export function makeServer() { /* Mirage code goes here */}

Completemos la implementación del makeServer eliminando el servidor Mirage JS que creamos en homepage.test.js y agregarlo al makeServer cuerpo de la función:

import { Server, Model, Factory } from 'miragejs'; export function makeServer() { let server = new Server({ models: { product: Model, }, factories: { product: Factory.extend({ name(i) { return `Product ${i}`; }, }), }, routes() { this.namespace = 'api'; this.get('/products', ({ products }) => { return products.all(); }); }, seeds(server) { server.createList('product', 5); }, }); return server;
}

Ahora todo lo que tienes que hacer es importar makeServer en tu prueba. Usar una sola instancia de Mirage Server es más limpio; de esta forma, no es necesario mantener dos instancias de servidor para los entornos de desarrollo y de prueba.

Después de importar el makeServer función, nuestra prueba ahora debería verse así:

import { makeServer } from '/path/to/server'; let server; beforeEach(() => { server = makeServer();
}); afterEach(() => { server.shutdown();
}); it('shows the products', function() { server.createList('product', 5); cy.visit('/'); cy.get('li.product').should('have.length', 5);
});

Así que ahora tenemos un servidor central de Mirage que nos sirve tanto para el desarrollo como para las pruebas. También puede utilizar el makeServer función para iniciar Mirage en desarrollo (ver primera parte de esta serie).

Su código de Mirage no debería entrar en producción. Por lo tanto, dependiendo de la configuración de su compilación, solo necesitará iniciar Mirage durante el modo de desarrollo.

Note: Lee mi artículo sobre cómo configurar API Mocking con Mirage y Vue.js para ver cómo lo hice en Vue para que pueda replicar en cualquier marco de front-end que use.

Entorno de prueba

Mirage tiene dos entornos: Desarrollo (predeterminado) y test. En el modo de desarrollo, el servidor de Mirage tendrá un tiempo de respuesta predeterminado de 400 ms (que puede personalizar. Consulte el tercer artículo de esta serie), registra todas las respuestas del servidor en la consola y carga las semillas de desarrollo.

Sin embargo, en el entorno de prueba, tenemos:

  • 0 retrasos para mantener nuestras pruebas rápidas
  • Mirage suprime todos los registros para no contaminar sus registros de CI
  • Mirage también ignorará el seeds() funcionan para que sus datos de inicialización se puedan usar únicamente para el desarrollo, pero no se filtrarán en sus pruebas. Esto ayuda a que sus pruebas sean deterministas.

Actualicemos nuestro makeServer para que podamos tener el beneficio del entorno de prueba. Para hacer eso, haremos que acepte un objeto con la opción de entorno (lo pondremos por defecto en desarrollo y lo anularemos en nuestra prueba). Nuestra server.js ahora debería verse así:

// src/server.js
import { Server, Model, Factory } from 'miragejs'; export function makeServer({ environment = 'development' } = {}) { let server = new Server({ environment, models: { product: Model, }, factories: { product: Factory.extend({ name(i) { return `Product ${i}`; }, }), }, routes() { this.namespace = 'api'; this.get('/products', ({ products }) => { return products.all(); }); }, seeds(server) { server.createList('product', 5); }, }); return server;
}

También tenga en cuenta que estamos pasando la opción de entorno a la instancia del servidor de Mirage usando el Taquigrafía de propiedad ES6. Ahora, con esto en su lugar, actualicemos nuestra prueba para anular el valor del entorno para probar. Nuestra prueba ahora se ve así:

import { makeServer } from '/path/to/server'; let server; beforeEach(() => { server = makeServer({ environment: 'test' });
}); afterEach(() => { server.shutdown();
}); it('shows the products', function() { server.createList('product', 5); cy.visit('/'); cy.get('li.product').should('have.length', 5);
});

Prueba AAA

Mirage fomenta un estándar para las pruebas llamado enfoque de prueba triple-A o AAA. Esto significa Arregle, Actúe y Afirmar. Ya podría ver esta estructura en nuestra prueba anterior:

it("shows all the products", function () { // ARRANGE server.createList("product", 5) // ACT cy.visit("/") // ASSERT cy.get("li.product").should("have.length", 5)
})

Es posible que deba romper este patrón, pero 9 de cada 10 veces debería funcionar bien para sus pruebas.

Probemos errores

Hasta ahora, hemos probado nuestra página de inicio para ver si tiene 5 productos, sin embargo, ¿qué pasa si el servidor no funciona o algo salió mal al buscar los productos? No necesitamos esperar a que el servidor esté inactivo para trabajar en cómo se vería nuestra interfaz de usuario en tal caso. Simplemente podemos simular ese escenario con Mirage.

Devolvemos un 500 (error del servidor) cuando el usuario está en la página de inicio. Como hemos visto en un artículo anterior, para personalizar las respuestas de Mirage hacemos uso de la clase Response. Importémoslo y escribamos nuestra prueba.

homepage.test.js
import { Response } from "miragejs" it('shows an error when fetching products fails', function() { server.get('/products', () => { return new Response( 500, {}, { error: "Can’t fetch products at this time" } ); }); cy.visit('/'); cy.get('div.error').should('contain', "Can’t fetch products at this time");
});

¡Qué mundo de flexibilidad! Simplemente anulamos la respuesta que Mirage devolvería para probar cómo se mostraría nuestra interfaz de usuario si fallara al obtener productos. nuestro general homepage.test.js El archivo ahora se vería así:

// homepage.test.js
import { Response } from 'miragejs';
import { makeServer } from 'path/to/server'; let server; beforeEach(() => { server = makeServer({ environment: 'test' });
}); afterEach(() => { server.shutdown();
}); it('shows the products', function() { server.createList('product', 5); cy.visit('/'); cy.get('li.product').should('have.length', 5);
}); it('shows an error when fetching products fails', function() { server.get('/products', () => { return new Response( 500, {}, { error: "Can’t fetch products at this time" } ); }); cy.visit('/'); cy.get('div.error').should('contain', "Can’t fetch products at this time");
});

Tenga en cuenta la modificación que hicimos a la /api/products el controlador solo vive en nuestra prueba. Eso significa que funciona como definimos anteriormente cuando está en modo de desarrollo.

Entonces, cuando ejecutamos nuestras pruebas, ambos deberían pasar.

Note: Creo que vale la pena señalar que los elementos que estamos consultando en Cypress deberían existir en la interfaz de usuario de su interfaz. Cypress no crea elementos HTML por usted.

Prueba de la página de detalles del producto

Finalmente, probemos la interfaz de usuario de la página de detalles del producto. Así que esto es lo que estamos probando:

  • El usuario puede ver el nombre del producto en la página de detalles del producto

Hagámoslo. Primero, creamos una nueva prueba para probar este flujo de usuario.

Aquí está la prueba:

it("shows the product’s name on the detail route", function() { let product = this.server.create('product', { name: 'Korg Piano', }); cy.visit(`/${product.id}`); cy.get('h1').should('contain', 'Korg Piano');
});

homepage.test.js finalmente debería verse así.

// homepage.test.js
import { Response } from 'miragejs';
import { makeServer } from 'path/to/server; let server; beforeEach(() => { server = makeServer({ environment: 'test' });
}); afterEach(() => { server.shutdown();
}); it('shows the products', function() { console.log(server); server.createList('product', 5); cy.visit('/'); cy.get('li.product').should('have.length', 5);
}); it('shows an error when fetching products fails', function() { server.get('/products', () => { return new Response( 500, {}, { error: "Can’t fetch products at this time" } ); }); cy.visit('/'); cy.get('div.error').should('contain', "Can’t fetch products at this time");
}); it("shows the product’s name on the detail route", function() { let product = server.create('product', { name: 'Korg Piano', }); cy.visit(`/${product.id}`); cy.get('h1').should('contain', 'Korg Piano');
});

Cuando ejecute sus pruebas, las tres deberían pasar.

Resumen

Ha sido divertido mostrarte el interior de Mirage JS en esta serie. Espero que haya estado mejor equipado para comenzar a tener una mejor experiencia de desarrollo front-end al usar Mirage para simular su servidor back-end. También espero que utilice el conocimiento de este artículo para escribir más pruebas de aceptación/interfaz de usuario/extremo a extremo para sus aplicaciones front-end.

  • Parte 1: Comprensión de los modelos y asociaciones de Mirage JS
  • Parte 2: Comprensión de fábricas, accesorios y serializadores
  • Parte 3: Comprender el tiempo, la respuesta y el traspaso
  • Parte 4: Uso de Mirage JS y Cypress para pruebas de interfaz de usuario
Editorial sensacional
(ra, il)

Fuente: https://www.smashingmagazine.com/2020/06/mirage-javascript-cypress-ui-testing/

punto_img

Información más reciente

punto_img