Catégories
Plugin et site web

Utilisation de Mirage JS et Cypress pour les tests d'interface utilisateur (partie 4) – Smashing Magazine

A propos de l'auteur

Kelvin Omereshone est le CTO de Quru Lab. Kelvin était auparavant ingénieur front-end chez myPadi.ng. Il est le créateur de la communauté Nuxtjs Africa et très passionné…
Plus à propos
Kelvin

Dans cette dernière partie de la série Deep Dive de Mirage JS, nous mettrons tout ce que nous avons appris dans la série passée à apprendre à effectuer des tests d'interface utilisateur avec Mirage JS.

L'une de mes citations préférées sur les tests de logiciels provient de la documentation Flutter. Ça dit:

"Comment pouvez-vous vous assurer que votre application continue de fonctionner pendant que vous ajoutez des fonctionnalités ou modifiez des fonctionnalités existantes? En écrivant des tests. "

Sur cette note, cette dernière partie de la série Mirage JS Deep Dive se concentrera sur l'utilisation de Mirage pour tester votre application frontale JavaScript.

Remarque: Cet article suppose un environnement Cypress. Cypress est un cadre de test pour les tests d'interface utilisateur. Vous pouvez cependant transférer les connaissances ici dans n'importe quel environnement ou cadre de test d'interface utilisateur que vous utilisez.

Lis Pièces précédentes De la série:

  • Partie 1: Comprendre les modèles et les associations Mirage JS
  • Partie 2: Comprendre les usines, les luminaires et les sérialiseurs
  • Partie 3: Comprendre le calendrier, la réponse et le passage

Introduction aux tests de l'interface utilisateur

Le test de l'interface utilisateur ou de l'interface utilisateur est une forme de test d'acceptation effectué pour vérifier la utilisateur flux de votre application frontale. Ces types de tests logiciels mettent l'accent sur l'utilisateur final qui est la personne réelle qui interagira avec votre application Web sur une variété d'appareils allant des ordinateurs de bureau aux ordinateurs portables en passant par les appareils mobiles. Celles-ci utilisateurs serait d'interfacer ou d'interagir avec votre application à l'aide de périphériques d'entrée tels qu'un clavier, une souris ou des écrans tactiles. Les tests d'interface utilisateur sont donc écrits pour imiter le utilisateur interaction avec votre application aussi proche que possible.

Prenons l'exemple d'un site Web de commerce électronique. Un scénario de test d'interface utilisateur typique serait:

  • L'utilisateur peut consulter la liste des produits lors de sa visite sur la page d'accueil.

D'autres scénarios de test d'interface utilisateur peuvent être:

  • L'utilisateur peut voir le nom d'un produit sur la page de détails du produit.
  • L'utilisateur peut cliquer sur le bouton «ajouter au panier».
  • L'utilisateur peut commander.

Vous avez l'idée, non?

En effectuant des tests d'interface utilisateur, vous vous baserez principalement sur vos états principaux, c'est-à-dire qu'ils ont renvoyé les produits ou une erreur? Le rôle joué par Mirage est de rendre ces états de serveur disponibles pour que vous puissiez les modifier selon vos besoins. Ainsi, au lieu de faire une demande réelle à votre serveur de production dans vos tests d'interface utilisateur, vous faites la demande au serveur factice Mirage.

Pour la partie restante de cet article, nous effectuerons des tests d'interface utilisateur sur une interface utilisateur d'application Web de commerce électronique fictive. Alors, commençons.

Notre premier test d'interface utilisateur

Comme indiqué précédemment, cet article suppose un environnement Cypress. Cypress permet de tester l'interface utilisateur sur le Web rapidement et facilement. Vous pouvez simuler les clics et la navigation et vous pouvez visiter par programme les itinéraires dans votre application. Consultez la documentation pour en savoir plus sur Cypress.

Donc, en supposant que Cypress et Mirage sont à notre disposition, commençons par définir une fonction proxy pour votre demande d'API. Nous pouvons le faire dans le support/index.js fichier de notre configuration Cypress. Collez simplement le code suivant dans:

// 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)))
      })
    })
  }
})

Ensuite, dans le fichier d'amorçage de votre application (main.js pour Vue, index.js pour React), nous utiliserons Mirage pour proxy les demandes d'API de votre application au handleFromCypress ne fonctionne que lorsque Cypress est en cours d'exécution. Voici le code pour cela:

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)
        })
      })
    },
  })
}

Avec cette configuration, chaque fois que Cypress est en cours d'exécution, votre application sait utiliser Mirage comme serveur factice pour toutes les demandes d'API.

Continuons à écrire des tests d’interface utilisateur. Nous allons commencer par tester notre page d'accueil pour voir si elle a 5 produits affiché. Pour ce faire dans Cypress, nous devons créer un homepage.test.js fichier dans le tests dossier à la racine du répertoire de votre projet. Ensuite, nous dirons à Cypress de procéder comme suit:

  • Visitez la page d'accueil i.e / route
  • alors affirmer s'il a des éléments li avec la classe de product et vérifie également s’ils sont au nombre de 5.

Voici le code:

// homepage.test.js
it('shows the products', () => {
  cy.visit('/');

  cy.get('li.product').should('have.length', 5);
});

Vous avez peut-être deviné que ce test échouerait car nous n'avons pas de serveur de production renvoyant 5 produits à notre application frontale. Alors que faisons-nous? On se moquerait du serveur de Mirage! Si nous apportons Mirage, il peut intercepter tous les appels réseau dans nos tests. Faisons-le ci-dessous et démarrons le serveur Mirage avant chaque test dans le beforeEach fonctionner et également le fermer dans le afterEach une fonction. le beforeEach et afterEach Les fonctions sont toutes deux fournies par Cypress et ont été rendues disponibles afin que vous puissiez exécuter du code avant et après chaque exécution de test dans votre suite de tests – d'où le nom. Voyons donc le code pour cela:

// 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)
})

D'accord, nous allons quelque part; nous avons importé le serveur de Mirage et nous le démarrons et le fermons dans beforeEach et afterEach fonctions respectivement. Commençons à nous moquer de notre ressource produit.


// 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);
});

Remarque: Vous pouvez toujours jeter un coup d'œil aux parties précédentes de cette série si vous ne comprenez pas les bits Mirage de l'extrait de code ci-dessus.

  • Partie 1: Présentation des modèles et des associations Mirage JS
  • Partie 2: Comprendre les usines, les luminaires et les sérialiseurs
  • Partie 3: Comprendre le calendrier, la réponse et le passage

D'accord, nous avons commencé à étoffer notre instance de serveur en créant le modèle de produit et également en créant le gestionnaire d'itinéraire pour le /api/products route. Cependant, si nous exécutons nos tests, il échouera car nous n’avons pas encore de produits dans la base de données Mirage.

Remplissons la base de données Mirage avec certains produits. Pour ce faire, nous aurions pu utiliser le create() sur notre instance de serveur, mais la création de 5 produits à la main semble assez fastidieuse. Il devrait y avoir une meilleure façon.

Ah oui, il y en a. Utilisons les usines (comme expliqué dans la deuxième partie de cette série). Nous créerions notre usine de produits comme suit:

// 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);
});

Enfin, nous utiliserons createList() pour créer rapidement les 5 produits que notre test doit réussir.

Faisons cela:

// 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);
});

Alors quand on lance notre test, ça passe!

Remarque: Après chaque test, le serveur de Mirage est arrêté et réinitialisé, donc aucun de cet état ne fuira à travers les tests.

Éviter plusieurs serveurs Mirage

Si vous suivez cette série, vous remarquerez que nous utilisions Mirage en développement pour intercepter nos demandes de réseau; nous avions un server.js fichier à la racine de notre application où nous avons installé Mirage. Dans l'esprit de DRY (ne vous répétez pas), je pense qu'il serait bon d'utiliser cette instance de serveur au lieu d'avoir deux instances distinctes de Mirage pour le développement et les tests. Pour ce faire (au cas où vous n’auriez pas de server.js fichier déjà), créez-en un dans votre projet src annuaire.

Remarque: Votre structure sera différente si vous utilisez un framework JavaScript mais l'idée générale est de configurer le fichier server.js dans la racine src de votre projet.

Donc, avec cette nouvelle structure, nous exportons une fonction dans server.js qui est responsable de la création de notre instance de serveur Mirage. Faisons cela:

// src/server.js

export function makeServer() { /* Mirage code goes here */}

Terminons la mise en œuvre du makeServer fonction en supprimant le serveur Mirage JS que nous avons créé dans homepage.test.js et l'ajouter à la makeServer corps de fonction:

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;
}

Il ne vous reste plus qu'à importer makeServer dans votre test. L'utilisation d'une seule instance de Mirage Server est plus propre; De cette façon, vous n'avez pas à gérer deux instances de serveur pour les environnements de développement et de test.

Après avoir importé le makeServer fonction, notre test devrait maintenant ressembler à ceci:

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);
});

Nous avons donc maintenant un serveur central Mirage qui nous sert à la fois de développement et de test. Vous pouvez également utiliser le makeServer pour démarrer Mirage en développement (voir la première partie de cette série).

Votre code Mirage ne devrait pas trouver sa voie en production. Par conséquent, selon la configuration de votre build, vous ne devrez démarrer Mirage qu'en mode développement. Lisez mon article sur la façon de configurer l'API Mocking avec Mirage et Vue.js pour voir comment je l'ai fait dans Vue afin que vous puissiez répliquer dans le cadre frontal que vous utilisez.

Environnement de test

Mirage a deux environnements: développement (par défaut) et tester. En mode développement, le serveur Mirage aura un temps de réponse par défaut de 400 ms (que vous pouvez personnaliser. Voir le troisième article de cette série pour cela), enregistre toutes les réponses du serveur à la console et charge les graines de développement.

Cependant, dans l'environnement de test, nous avons:

  • 0 délais pour garder nos tests rapides
  • Mirage supprime tous les journaux afin de ne pas polluer vos journaux CI
  • Mirage ignorera également seeds() fonctionner de sorte que vos données de départ puissent être utilisées uniquement pour le développement, mais ne fuiront pas dans vos tests. Cela permet de garder vos tests déterministes.

Mettons à jour notre makeServer afin que nous puissions bénéficier de l'environnement de test. Pour ce faire, nous lui ferions accepter un objet avec l'option d'environnement (nous le mettrons par défaut en développement et le remplacerons dans notre test). Notre server.js devrait maintenant ressembler à ceci:

// 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;
}

Notez également que nous transmettons l'option d'environnement à l'instance de serveur Mirage en utilisant la propriété raccourcie ES6. Maintenant que cela est en place, mettons à jour notre test pour remplacer la valeur d'environnement à tester. Notre test ressemble maintenant à ceci:

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);
});

Test AAA

Mirage encourage une norme de test appelée approche de test triple-A ou AAA. Cela signifie Organiser, Acte et Affirmer. Vous pouvez déjà voir cette structure dans notre test ci-dessus:

it("shows all the products", function () {
  // ARRANGE
  server.createList("product", 5)

  // ACT
  cy.visit("/")

  // ASSERT
  cy.get("li.product").should("have.length", 5)
})

Vous devrez peut-être briser ce modèle, mais 9 fois sur 10, cela fonctionnera très bien pour vos tests.

Essayons les erreurs

Jusqu'à présent, nous avons testé notre page d'accueil pour voir si elle contient 5 produits, mais que se passe-t-il si le serveur est en panne ou que quelque chose ne va pas avec la récupération des produits? Nous n'avons pas besoin d'attendre que le serveur soit en panne pour voir à quoi ressemblerait notre interface utilisateur dans un tel cas. Nous pouvons simplement simuler ce scénario avec Mirage.

Renvoyons un 500 (erreur du serveur) lorsque l'utilisateur est sur la page d'accueil. Comme nous l'avons vu dans un article précédent, pour personnaliser les réponses Mirage, nous utilisons la classe Response. Importons-le et écrivons notre test.

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");
});

Quel monde de flexibilité! Nous remplaçons simplement la réponse que Mirage retournerait afin de tester la façon dont notre interface utilisateur s'afficherait en cas d'échec de la récupération des produits. Notre ensemble homepage.test.js Le fichier ressemblerait maintenant à ceci:

// 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");
});

Notez la modification que nous avons apportée au /api/products gestionnaire ne vit que dans notre test. Cela signifie que cela fonctionne comme nous l'avons défini précédemment lorsque vous êtes en mode développement.

Donc, lorsque nous exécutons nos tests, les deux devraient réussir.

Remarque: Je pense qu'il vaut la peine de noter que les éléments que nous recherchons dans Cypress devraient exister dans votre interface utilisateur frontale. Cypress ne crée pas d’éléments HTML pour vous.

Test de la page Détails du produit

Enfin, nous testerions l'interface utilisateur de la page de détail du produit. Voici donc ce que nous testons:

  • L'utilisateur peut voir le nom du produit sur la page de détail du produit

Allons-y. Tout d'abord, nous créons un nouveau test pour tester ce flux d'utilisateurs.

Voici le test:

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');
});

Votre homepage.test.js devrait enfin ressembler à ceci.

// 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');
});

Lorsque vous exécutez vos tests, les trois devraient réussir.

Emballer

C'était amusant de vous montrer les secrets de Mirage JS dans cette série. J'espère que vous avez été mieux équipé pour commencer à avoir une meilleure expérience de développement frontal en utilisant Mirage pour simuler votre serveur principal. J'espère également que vous utiliserez les connaissances de cet article pour rédiger davantage de tests d'acceptation / d'interface utilisateur / de bout en bout pour vos applications frontales.

  • Partie 1: Comprendre les modèles et les associations Mirage JS
  • Partie 2: Comprendre les usines, les luminaires et les sérialiseurs
  • Partie 3: Comprendre le calendrier, la réponse et le passage
  • Partie 4: Utilisation de Mirage JS et Cypress pour les tests d'interface utilisateur
Smashing Editorial(rail)

Laisser un commentaire

Votre adresse de messagerie ne sera pas publiée. Les champs obligatoires sont indiqués avec *