Site icon Blog ARC Optimizer

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


À propos de l'auteur

Kelvin Omereshone est le directeur technique 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é…
En savoir plus sur
Kelvin

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

L'une de mes citations préférées à propos des tests de logiciels provient de la documentation Flutter. Il dit:

«Comment pouvez-vous vous assurer que votre application continue de fonctionner lorsque 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.

Lire Parties précédentes De la série:

  • Partie 1 : Comprendre les modèles et associations Mirage JS
  • Partie 2 : Comprendre les usines, les montages et les sérialiseurs
  • Partie 3 : Comprendre la synchronisation, 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 les utilisateurs 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. Ces utilisateurs interfaceraient ou interagiraient avec votre application à l'aide de périphériques d'entrée tels qu'un clavier, une souris ou des écrans tactiles. Par conséquent, les tests d'interface utilisateur sont écrits pour reproduire au plus près l'interaction de l'utilisateur avec votre application.

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

  • L'utilisateur peut afficher la liste des produits lorsqu'il visite 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 les détails du produit page.
  • L'utilisateur peut cliquer sur le bouton "ajouter au panier".
  • L'utilisateur peut commander.

Vous avez l'idée, n'est-ce pas?

En faisant des tests d'interface utilisateur, vous vous ferez surtout à vos états back-end, c'est-à-dire a-t-il 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 application Web de commerce électronique fictive. UI. Commençons donc.

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 fichier support / index.js de notre configuration Cypress. Collez simplement le code suivant dans:

 // cypress / support / index.js
Cypress.on ("window: before: load", (win) => {
  win.handleFromCypress = fonction (demande) {
    return fetch (request.url, {
      méthode: request.method,
      en-têtes: request.requestHeaders,
      body: request.requestBody,
    }). Puis ((res) => {
      laisser le contenu =
        res.headers.map ["content-type"] === "application / json"
          ? res.json ()
          : res.text ()
      retourner une nouvelle promesse ((résoudre) => {
        content.then ((body) => résoudre ([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 vers 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) {
  nouveau serveur ({
    environnement: "test",
    routes () {
      let methods = ["get", "put", "patch", "post", "delete"]
      methods.forEach ((méthode) => {
        this [method] ("/ *", async (schéma, requête) => {
          let [status, headers, body] = attendre window.handleFromCypress (demande)
          retourner une nouvelle réponse (statut, en-têtes, corps)
        })
      })
    },
  })
}

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 d'écrire quelques tests d'interface utilisateur. Nous allons commencer par tester notre page d'accueil pour voir si elle affiche 5 produits . Pour ce faire dans Cypress, nous devons créer un fichier homepage.test.js dans le dossier tests à la racine de votre répertoire de projet. Ensuite, nous dirons à Cypress de faire ce qui suit:

  • Visitez la page d'accueil c.-à-d. / route
  • Puis affirmez s'il a des éléments li avec la classe de produit et vérifie également s’ils sont au nombre de 5.

Voici le code:

 // homepage.test.js
il ('montre les produits', () => {
  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 la fonction avantChaque et arrêtons-le également dans la fonction afterEach . Les fonctions beforeEach et afterEach sont toutes deux fournies par Cypress et elles ont été mises à disposition afin que vous puissiez exécuter du code avant et après chaque test dans votre suite de tests – d'où le nom. Voyons donc le code pour cela:

 // homepage.test.js
importer {Server} depuis "miragejs"

laisser le serveur

beforeEach (() => {
  serveur = nouveau serveur ()
})

afterEach (() => {
  server.shutdown ()
})

il ("montre les produits", fonction () {
  cy.visit ("/")

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

D'accord, nous arrivons quelque part; nous avons importé le serveur de Mirage et nous le démarrons et le fermons respectivement dans avant chaque et après chaque fonctions. Commençons par nous moquer de nos ressources produit.


// homepage.test.js
importer {Server, Model} depuis 'miragejs';

laisser le serveur;

beforeEach (() => {
  serveur = nouveau serveur ({
    des modèles: {
      modèle du produit,
    },

    routes () {
      this.namespace = 'api';

      this.get ('produits', ({produits}, demande) => {
        return products.all ();
      });
    },
  });
});

afterEach (() => {
  server.shutdown ();
});

it ('affiche les produits', function () {
  cy.visit ('/');

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

Remarque : Vous pouvez toujours jeter un œ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 : Comprendre les modèles et associations Mirage JS
  • Partie 2 : Comprendre les usines, les installations et les sérialiseurs
  • Partie 3 : Comprendre la synchronisation, 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 la route / api / products . 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 la méthode create () sur notre instance de serveur, mais la création de 5 produits à la main semble assez fastidieuse. Il devrait y avoir un meilleur moyen.

Ah oui, il y en a un. 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
importer {Server, Model, Factory} depuis 'miragejs';

laisser le serveur;

beforeEach (() => {
  serveur = nouveau serveur ({
    des modèles: {
      modèle du produit,
    },
     des usines: {
      produit: Factory.extend ({
        nom (i) {
            retourner `Produit $ {i}`
        }
      })
    },

    routes () {
      this.namespace = 'api';

      this.get ('produits', ({produits}, demande) => {
        return products.all ();
      });
    },
  });
});

afterEach (() => {
  server.shutdown ();
});

it ('affiche les produits', function () {
  cy.visit ('/');

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

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

Faisons ceci:

 // homepage.test.js
importer {Server, Model, Factory} depuis 'miragejs';

laisser le serveur;

beforeEach (() => {
  serveur = nouveau serveur ({
    des modèles: {
      modèle du produit,
    },
     des usines: {
      produit: Factory.extend ({
        nom (i) {
            retourner `Produit $ {i}`
        }
      })
    },

    routes () {
      this.namespace = 'api';

      this.get ('produits', ({produits}, demande) => {
        return products.all ();
      });
    },
  });
});

afterEach (() => {
  server.shutdown ();
});

it ('affiche les produits', function () {
  server.createList ("produit", 5)
  cy.visit ('/');

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

Donc, lorsque nous exécutons notre test, il passe!

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

Éviter les multiples Mirage Server

Si vous suivez cette série, vous remarquerez que nous utilisons Mirage en développement pour intercepter nos requêtes réseau; nous avions un fichier server.js à 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 ne disposez pas déjà d'un fichier server.js ), créez-en un dans le répertoire de votre projet src .

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-le:

 // src / server.js

fonction d'exportation makeServer () {/ * Le code Mirage va ici * /}

Terminons l'implémentation de la fonction makeServer en supprimant le serveur Mirage JS que nous avons créé dans homepage.test.js et en l'ajoutant à la fonction makeServer body:

 import {Server, Model, Factory} from 'miragejs';

fonction d'exportation makeServer () {
  laisser serveur = nouveau serveur ({
    des modèles: {
      modèle du produit,
    },
    des usines: {
      produit: Factory.extend ({
        nom (i) {
          return `Product $ {i}`;
        },
      }),
    },
    routes () {
      this.namespace = 'api';

      this.get ('/ produits', ({produits}) => {
        return products.all ();
      });
    },
    graines (serveur) {
      server.createList ('produit', 5);
    },
  });
  serveur de retour;
}

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é la fonction makeServer notre test devrait maintenant ressembler à ceci:

 import {makeServer} de '/ chemin / vers / serveur';

laisser le serveur;

beforeEach (() => {
  serveur = makeServer ();
});

afterEach (() => {
  server.shutdown ();
});

it ('affiche les produits', function () {
  server.createList ('produit', 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 la fonction makeServer pour démarrer Mirage en cours de développement (voir 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 comment 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 [19659002] Mirage a deux environnements: développement (par défaut) et test . 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élai pour maintenir nos tests rapides
  • Mirage supprime tous les journaux afin de ne pas polluer vos journaux CI
  • Mirage ignorera également les graines () fonctionnent de manière à ce que vos données de départ puissent être utilisées uniquement pour le développement mais ne fuient 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
importer {Server, Model, Factory} depuis 'miragejs';

fonction d'exportation makeServer ({environnement = 'développement'} = {}) {
  laisser serveur = nouveau serveur ({
    environnement,

    des modèles: {
      modèle du produit,
    },
    des usines: {
      produit: Factory.extend ({
        nom (i) {
          return `Product $ {i}`;
        },
      }),
    },

    routes () {
      this.namespace = 'api';

      this.get ('/ produits', ({produits}) => {
        return products.all ();
      });
    },
    graines (serveur) {
      server.createList ('produit', 5);
    },
  });
  serveur de retour;
}

Notez également que nous transmettons l'option d'environnement à l'instance de serveur Mirage en utilisant le raccourci de propriété 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} de '/ path / to / server';

laisser le serveur;

beforeEach (() => {
  server = makeServer ({environnement: 'test'});
});

afterEach (() => {
  server.shutdown ();
});

it ('affiche les produits', function () {
  server.createList ('produit', 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 Arrange Act et Assert . Vous pouvez déjà voir cette structure dans notre test ci-dessus:

 it ("affiche tous les produits", fonction () {
  // ORGANISER
  server.createList ("produit", 5)

  // AGIR
  cy.visit ("/")

  // ASSERTER
  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 , cependant, 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
importer {Response} depuis "miragejs"

it ('affiche une erreur lors de l'échec de la récupération des produits', function () {
  server.get ('/ produits', () => {
    retourner une nouvelle réponse (
      500,
      {},
      {erreur: "Impossible de récupérer des produits pour le moment"}
    );
  });

  cy.visit ('/');

  cy.get ('div.error'). should ('contient', "Impossible de récupérer les produits pour le moment");
});

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 fichier global homepage.test.js ressemblerait maintenant à ceci:

 // homepage.test.js
importer {Response} depuis 'miragejs';
importer {makeServer} à partir de 'chemin / vers / serveur';

laisser le serveur;

beforeEach (() => {
  server = makeServer ({environnement: 'test'});
});

afterEach (() => {
  server.shutdown ();
});

it ('affiche les produits', function () {
  server.createList ('produit', 5);

  cy.visit ('/');

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

it ('affiche une erreur lors de l'échec de la récupération des produits', function () {
  server.get ('/ produits', () => {
    retourner une nouvelle réponse (
      500,
      {},
      {erreur: "Impossible de récupérer des produits pour le moment"}
    );
  });

  cy.visit ('/');

  cy.get ('div.error'). should ('contient', "Impossible de récupérer les produits pour le moment");
});

Notez la modification que nous avons apportée au gestionnaire / api / products 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 de développement.

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

Note : Je crois qu'il vaut la peine de noter que les éléments que nous que vous recherchez dans Cypress devrait exister dans votre interface utilisateur frontale. Cypress ne crée pas d'éléments HTML pour vous.

Test de la page de détails du produit

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

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

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

Voici le test:

 it ("affiche le nom du produit sur l'itinéraire détaillé", function () {
  laissez product = this.server.create ('product', {
    nom: «Korg Piano»,
  });

  cy.visit (`/ $ {product.id}`);

  cy.get ('h1'). should ('contient', 'Korg Piano');
});

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

 // homepage.test.js
importer {Response} depuis 'miragejs';
importer {makeServer} de 'chemin / vers / serveur;

laisser le serveur;

beforeEach (() => {
  server = makeServer ({environnement: 'test'});
});

afterEach (() => {
  server.shutdown ();
});

it ('affiche les produits', function () {
  console.log (serveur);
  server.createList ('produit', 5);

  cy.visit ('/');

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

it ('affiche une erreur lors de l'échec de la récupération des produits', function () {
  server.get ('/ produits', () => {
    retourner une nouvelle réponse (
      500,
      {},
      {erreur: "Impossible de récupérer des produits pour le moment"}
    );
  });

  cy.visit ('/');

  cy.get ('div.error'). should ('contient', "Impossible de récupérer les produits pour le moment");
});

il ("affiche le nom du produit sur l'itinéraire détaillé", function () {
  laissez product = server.create ('product', {
    nom: «Korg Piano»,
  });

  cy.visit (`/ $ {product.id}`);

  cy.get ('h1'). should ('contient', 'Korg Piano');
});

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

Récapitulation

C'est amusant de vous montrer les caractéristiques 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 écrire plus de tests d'acceptation / d'interface utilisateur / de bout en bout pour vos applications frontales.

  • Partie 1 : Comprendre les modèles et associations Mirage JS
  • Partie 2 : Comprendre les usines, les montages et les sérialiseurs
  • Partie 3 : Comprendre la synchronisation, la réponse et le passage
  • Partie 4: Utiliser Mirage JS et Cypress pour les tests d'interface utilisateur
(ra, il)




Source link
Quitter la version mobile