Catégories
Astuces et Design

N'attendez pas! Se moquer de l'API

Aujourd'hui, nous avons un couplage lâche entre l'extrémité avant et l'extrémité arrière des applications Web. Ils sont généralement développés par des équipes distinctes et il n'est pas facile de synchroniser ces équipes et la technologie. Pour résoudre une partie de ce problème, nous pouvons «simuler» le serveur d'API que la technologie back-end créerait et développerait normalement comme si l'API ou les points de terminaison existaient déjà.

Le terme le plus couramment utilisé pour créer un composant simulé ou "truquer" est railleur. La simulation vous permet de simuler l'API sans (idéalement) changer le front-end. Il existe de nombreuses façons de se moquer, et c'est ce qui la rend si effrayante pour la plupart des gens, du moins à mon avis.

Voyons à quoi devrait ressembler une bonne simulation d'API et comment implémenter une API fictive dans une application nouvelle ou existante.

Notez que l'implémentation que je suis sur le point de montrer est indépendante du framework – elle peut donc être utilisée avec n'importe quel framework ou application JavaScript vanilla.

Mirage: le cadre moqueur

L'approche moqueuse que nous allons utiliser s'appelle Mirage, ce qui est quelque peu nouveau. J'ai testé de nombreux frameworks moqueurs et je viens de découvrir celui-ci, et cela a changé la donne pour moi.

Mirage est commercialisé comme un framework convivial avec une interface moderne. Il fonctionne dans votre navigateur, côté client, en interceptant XMLHttpRequest et Récupérer les demandes.

Nous allons créer une application simple avec une API fictive et couvrir certains problèmes courants en cours de route.

Configuration du Mirage

Créons une de ces applications de tâches standard pour démontrer les moqueries. J'utiliserai Vue comme framework de choix, mais bien sûr, vous pouvez utiliser autre chose puisque nous travaillons avec une approche indépendante du framework.

Alors, allez-y et installez Mirage dans votre projet:

# Using npm
npm i miragejs -D


# Using Yarn
yarn add miragejs -D

Pour commencer à utiliser Mirage, nous devons configurer un "serveur" (entre guillemets, car il s'agit d'un faux serveur). Avant de passer à la configuration, je vais couvrir la structure de dossiers qui fonctionne le mieux.

/
├── public
├── src
│   ├── api
│   │   └── mock
│   │       ├── fixtures
│   │       │   └── get-tasks.js
│   │       └── index.js
│   └── main.js
├── package.json
└── package-lock.json

Dans un mock répertoire, ouvrez un nouveau index.js fichier et définissez votre serveur fictif:

// api/mock/index.js
import { Server } from 'miragejs';


export default function ({ environment = 'development' } = {}) {
  return new Server({
    environment,


    routes() {
      // We will add our routes here
    },
  });
}

L'argument d'environnement que nous ajoutons à la signature de la fonction n'est qu'une convention. Nous pouvons passer dans un environnement différent selon les besoins.

Maintenant, ouvrez le fichier de démarrage de votre application. Dans notre cas, c'est lui src/main.js fichier puisque nous travaillons avec Vue. Importez votre createServer fonction, et appelez-le dans le environnement de développement.

// main.js
import createServer from './mock'


if (process.env.NODE_ENV === 'development') {
    createServer();
}

Nous utilisons le process.env.NODE_ENV variable d'environnement ici, qui est une variable globale commune. Le conditionnel permet à Mirage d'être secoué en production, par conséquent, cela n'affectera pas votre bundle de production.

C'est tout ce dont nous avons besoin pour mettre en place Mirage! C'est ce genre de facilité qui rend le DX de Mirage si agréable.

Notre createServer la fonction est par défaut development environnement pour simplifier cet article. Dans la plupart des cas, ce sera par défaut test puisque, dans la plupart des applications, vous appellerez createServer une fois en mode développement mais plusieurs fois dans les fichiers de test.

Comment ça fonctionne

Avant de faire notre première demande, voyons rapidement comment fonctionne Mirage.

Mirage est un côté client cadre moqueur, ce qui signifie que toutes les moqueries se produiront dans le navigateur, ce que Mirage fait en utilisant la bibliothèque Pretender. Pretender remplacera temporairement le natif XMLHttpRequest et Récupérez les configurations, interceptez toutes les demandes et dirigez-les vers un petit service simulé auquel le Mirage s'accroche.

Si vous ouvrez DevTools et accédez à l'onglet Réseau, vous ne verrez aucune requête Mirage. C'est parce que la demande est interceptée et traitée par Mirage (via Pretender dans le back-end). Mirage enregistre toutes les demandes, ce que nous aborderons dans un instant.

Faisons des demandes!

Créons une demande à un /api/tasks point de terminaison qui renverra une liste de tâches que nous allons afficher dans notre application de tâches. Notez que j'utilise axios pour récupérer les données. C’est juste ma préférence personnelle. Encore une fois, Mirage fonctionne avec des XMLHttpRequest, Fetch et toute autre bibliothèque.

// components/tasks.vue
export default {
  async created() {
    try {
      const { data } = await axios.get('/api/tasks'); // Fetch the data
      this.tasks = data.tasks;
    } catch(e) {
      console.error(e);
    }
  }
};

Ouverture de votre console JavaScript – il devrait y avoir une erreur de Mirage là-dedans:

Mirage: Your app tried to GET '/api/tasks', but there was no route defined to handle this request.

Cela signifie que Mirage est en cours d'exécution, mais que le routeur n'a pas encore été simulé. Résolvons ce problème en ajoutant cette route.

Demandes moqueuses

À l'intérieur de notre mock/index.js fichier, il y a un routes() crochet. Les gestionnaires de routes nous permettent de définir quelles URL doivent être gérées par le serveur Mirage.

Pour définir un gestionnaire de routeur, nous devons l'ajouter à l'intérieur du routes() fonction.

// mock/index.js
export default function ({ environment = 'development' } = {}) {
    // ...
    routes() {
      this.get('/api/tasks', () => ({
        tasks: (
          { id: 1, text: "Feed the cat" },
          { id: 2, text: "Wash the dishes" },
          //...
        ),
      }))
    },
  });
}

le routes() hook est la façon dont nous définissons nos gestionnaires de route. Utilisant un this.get() la méthode nous permet de nous moquer GET demandes. Le premier argument de toutes les fonctions de requête est l'URL que nous traitons, et le second argument est une fonction qui répond avec certaines données.

À noter, Mirage accepte n'importe quel type de requête HTTP, et chaque type a la même signature:

this.get('/tasks', (schema, request) => { ... });
this.post('/tasks', (schema, request) => { ... });
this.patch('/tasks/:id', (schema, request) => { ... });
this.put('/tasks/:id', (schema, request) => { ... });
this.del('/tasks/:id', (schema, request) => { ... });
this.options('/tasks', (schema, request) => { ... });

Nous discuterons de la schema et request paramètres de la fonction de rappel dans un instant.

Avec cela, nous avons réussi à nous moquer de notre route et nous devrions voir à l'intérieur de notre console une réponse réussie de Mirage.

Capture d'écran d'une réponse Mirage dans la console montrant les données de deux objets de tâche avec les ID 1 et 2.

Travailler avec des données dynamiques

Essayer d'ajouter une nouvelle tâche dans notre application ne sera pas possible, car nos données dans le GET La réponse a des valeurs codées en dur. La solution de Mirage consiste à fournir une couche de données légère qui agit comme une base de données. Corrigeons ce que nous avons jusqu'à présent.

Comme le routes() crochet, Mirage définit un seeds() crochet. Cela nous permet de créer des données initiales pour le serveur. Je vais déplacer le GET données au seeds() hook où je vais le pousser dans la base de données Mirage.

seeds(server) {
  server.db.loadData({
    tasks: (
      { id: 1, text: "Feed the cat" },
      { id: 2, text: "Wash the dishes" },
    ),
  })
},

J'ai déplacé nos données statiques du GET méthode pour seeds() hook, où ces données sont chargées dans une fausse base de données. Maintenant, nous devons refactoriser notre GET méthode pour renvoyer les données de cette base de données. C'est en fait assez simple – le premier argument de la fonction de rappel de tout route() méthode est le schéma.

this.get('/api/tasks', (schema) => {
  return schema.db.tasks;
})

Nous pouvons maintenant ajouter de nouvelles tâches à notre application en créant un POST demande:

async addTask() {
  const { data } = await axios.post('/api/tasks', { data: this.newTask });
  this.tasks.push(data);
  this.newTask = {};
},

Nous nous moquons de cette route à Mirage en créant un POST /api/tasks gestionnaire d'itinéraire:

this.post('/tasks', (schema, request) => {})

En utilisant le deuxième paramètre de la fonction de rappel, nous pouvons voir la requête envoyée.

Capture d'écran de la requête du serveur moqueur. La propriété requestBody est surlignée en jaune et contient des données de texte indiquant Hello CSS-Tricks.

À l'intérieur de requestBody property sont les données que nous avons envoyées. Cela signifie que nous pouvons désormais créer une nouvelle tâche.

this.post('/api/tasks', (schema, request) => {
  // Take the send data from axios.
  const task = JSON.parse(request.requestBody).data


  return schema.db.tasks.insert(task)
})

le id de la tâche sera définie par défaut par la base de données du Mirage. Ainsi, il n'est pas nécessaire de garder une trace des identifiants et de les envoyer avec votre demande – tout comme un vrai serveur.

Routes dynamiques? Sûr!

La dernière chose à couvrir, ce sont les itinéraires dynamiques. Ils nous permettent d'utiliser un segment dynamique dans notre URL, ce qui est utile pour supprimer ou mettre à jour une seule tâche à faire dans notre application.

Notre demande de suppression doit aller à /api/tasks/1, /api/tasks/2, etc. Mirage nous permet de définir un segment dynamique dans l'URL, comme ceci:

this.delete('/api/tasks/:id', (schema, request) => {
  // Return the ID from URL.
  const id = request.params.id;


  return schema.db.tasks.remove(id);
})

En utilisant un deux-points (:) dans l'URL est la façon dont nous définissons un segment dynamique dans notre URL. Après les deux points, nous spécifions le nom du segment qui, dans notre cas, est appelé id et correspond à l'ID d'une tâche spécifique. On peut accéder à la valeur du segment via le request.params objet, où le nom de la propriété correspond au nom du segment – request.params.id. Ensuite, nous utilisons le schéma pour supprimer un élément avec ce même ID de la base de données Mirage.

Si vous avez remarqué, tous mes itinéraires jusqu'à présent sont précédés du préfixe api/. Ecrire ceci encore et encore peut être fastidieux et vous voudrez peut-être le rendre plus facile. Mirage propose le namespace propriété qui peut aider. À l'intérieur du crochet de routes, nous pouvons définir le namespace propriété afin que nous n'ayons pas à écrire cela à chaque fois.

routes() {
 // Prefix for all routes.
 this.namespace = '/api';


 this.get('/tasks', () => { ... })
 this.delete('/tasks/:id', () => { ... })
 this.post('/tasks', () => { ... })
}

OK, intégrons ceci dans un existant app

Jusqu'à présent, tout ce que nous avons examiné intègre Mirage dans une nouvelle application. Mais qu'en est-il de l'ajout de Mirage à une application existante? Mirage vous a couvert pour que vous n'ayez pas à vous moquer de l'ensemble de votre API.

La première chose à noter est que l'ajout de Mirage à une application existante générera une erreur si le site fait une demande qui n'est pas traitée par Mirage. Pour éviter cela, nous pouvons dire à Mirage de traiter toutes les demandes non traitées.

routes() {
  this.get('/tasks', () => { ... })
  
  // Pass through all unhandled requests.
  this.passthrough()
}

Maintenant, nous pouvons développer au-dessus d'une API existante avec Mirage ne gérant que les parties manquantes de notre API.

Mirage peut même modifier l'URL de base dont il capture les requêtes. Ceci est utile car, généralement, un serveur ne fonctionne pas sur localhost:3000 mais plutôt sur un domaine personnalisé.

routes() {
 // Set the base route.
 this.urlPrefix = 'https://devenv.ourapp.example';


 this.get('/tasks', () => { ... })
}

Désormais, toutes nos requêtes pointeront vers le vrai serveur API, mais Mirage les interceptera comme il l'a fait lorsque nous l'avons configuré avec une nouvelle application. Cela signifie que la transition de Mirage vers la vraie API est sacrément transparente – supprimez la route du serveur fictif et tout va bien.

Emballer

Au cours des cinq dernières années, j'ai utilisé de nombreux frameworks moqueurs, mais je n'ai jamais vraiment aimé aucune des solutions disponibles. C'était jusqu'à récemment, lorsque mon équipe a été confrontée à un besoin de solution moqueuse et que j'ai découvert Mirage.

D'autres solutions, comme le serveur JSON couramment utilisé, sont des processus externes qui doivent s'exécuter parallèlement au front-end. De plus, ils ne sont souvent rien de plus qu'un serveur Express avec des fonctions utilitaires en plus. Le résultat est que les développeurs front-end comme nous doivent connaître les middlewares, NodeJS et le fonctionnement des serveurs… des choses que beaucoup d’entre nous ne veulent probablement pas gérer. D'autres tentatives, comme Mockoon, ont une interface complexe tout en manquant de fonctionnalités indispensables. Il existe un autre groupe de frameworks qui ne sont utilisés que pour les tests, comme le populaire SinonJS. Malheureusement, ces frameworks ne peuvent pas être utilisés pour se moquer du comportement normal.

Mon équipe a réussi à créer un serveur fonctionnel qui nous permet d'écrire du code front-end comme si nous travaillions avec un vrai back-end. Nous l'avons fait en écrivant la base de code frontale sans aucun processus ou serveur externe nécessaire pour s'exécuter. C'est pourquoi j'aime Mirage. Il est vraiment simple à configurer, mais suffisamment puissant pour gérer tout ce qui lui est lancé. Vous pouvez l'utiliser pour les applications de base qui renvoient un tableau statique vers des applications back-end complètes, qu'il s'agisse d'une application nouvelle ou existante.

Il y a beaucoup plus à Mirage au-delà des implémentations que nous avons couvertes ici. Un exemple fonctionnel de ce que nous avons couvert peut être trouvé sur GitHub. (Fait amusant: Mirage fonctionne également avec GraphQL!) Mirage a une documentation bien écrite qui comprend un tas de didacticiels étape par étape, alors assurez-vous de la vérifier.

Laisser un commentaire

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