• 8 heures
  • Moyenne

Ce cours est visible gratuitement en ligne.

course.header.alt.is_certifying

J'ai tout compris !

Mis à jour le 22/11/2023

Rédigez des cas de test API et ajoutez des commandes

Graphique de bannière

Testez une API

Une API (Application Programming Interface) est un ensemble de règles et de protocoles qui permettent à différentes applications de communiquer et d'échanger des données entre elles de manière standardisée. Elle définit les méthodes, les formats de données et les conventions d'accès nécessaires pour que les applications puissent interagir.

Les API jouent un rôle essentiel dans le développement moderne des logiciels. Elles permettent aux développeurs d'accéder aux fonctionnalités d'une application ou d'un service tiers de manière structurée et sécurisée. Les API sont utilisées dans de nombreux domaines, tels que le web, les applications mobiles, l'internet des objets, le cloud computing, etc.

J’ai entendu parler de Swagger dans le contexte des API. C’est quoi, au juste ?

Swagger, maintenant connu sous le nom d'OpenAPI Specification (OAS), est un framework de développement d'API qui permet de concevoir, de documenter, de construire et de déployer des services web RESTful. Il offre un moyen standardisé de décrire les fonctionnalités d'une API web, y compris les points d'extrémité disponibles, les opérations HTTP prises en charge, les paramètres d'entrée et de sortie, les types de données, les erreurs possibles, et bien plus encore.

Les principales caractéristiques de Swagger sont les suivantes :

  • Documentation automatique : Swagger permet de générer automatiquement une documentation détaillée pour votre API. Cette documentation est interactive et permet aux développeurs/testeurs de comprendre rapidement comment utiliser votre API.

  • Validation de la conformité : Il permet de définir des schémas de données pour les entrées et les sorties de l'API, ce qui permet de valider automatiquement les données entrantes et sortantes pour s'assurer qu'elles sont conformes aux spécifications.

  • Génération de code client : À partir de la description de l'API, il est possible de générer automatiquement du code client dans de nombreux langages de programmation. Cela simplifie l'intégration de l'API dans les applications clientes.

  • Test d'API : Swagger fournit souvent des outils pour tester les points d'extrémité de l'API directement depuis la documentation, ce qui facilite le débogage et la validation de l'API.

  • Compatibilité entre langages : Puisque Swagger utilise des spécifications standardisées, les API documentées avec Swagger peuvent être utilisées par des clients dans plusieurs langages de programmation.

Lors du développement et de l'intégration d'une API, il est crucial de s'assurer que celle-ci fonctionne correctement et qu'elle répond aux attentes en termes de performances, de fiabilité et de sécurité. C'est là qu'intervient Cypress. En utilisant Cypress pour tester une API, vous pouvez :

  • simuler des requêtes HTTP ;

  • vérifier les réponses reçues ; 

  • effectuer des assertions sur les données retournées ;

  • vérifier les codes de statut. 

Les tests API avec Cypress permettent de s'assurer que votre API fonctionne correctement et de détecter rapidement les éventuels problèmes ou erreurs. Ils vous aident à :

  • valider le bon fonctionnement de vos endpoints ;

  • vérifier la conformité des données retournées ;

  • identifier les erreurs de traitement ;

  • garantir la stabilité et la performance de votre API.

Revenons à notre projet et rendez-vous sur http://localhost:8081/ où vous trouverez la liste des appels API disponibles pour le projet Tech&Buy. 

Capture d'écran montrant une liste d'appels API GET pour le site Tech&Buy
Liste des appels disponibles pour Tech&Buy

Comme vous pouvez le voir, l’API ne contient que des appels GET.

Vous allez tester l’appel GET de /products qui renvoie la liste des produits.

Pour cela, créez un nouveau fichier dans le dossier e2e, nommé api.cy.js :  

Capture d'écran montrant l'arborescence des dossiers et fichiers dans VSCode. On voit que le fichier api.cy.js a été ajouté dans le dossier e2e.
Ajout du fichier api.cy.js dans le dossier e2e

Au préalable, ajoutez une variable d’environnement apiUrl dans le fichier cypress.config.js : 

const { defineConfig } = require("cypress");

module.exports = defineConfig({
  env: {
    apiUrl: "http://localhost:8081"
  },
  e2e: {
    setupNodeEvents(on, config) {
      // implement node event listeners here
    },
    baseUrl: "http://localhost:8080/",

  },
});

Le test sera :

const apiProduct = `${Cypress.env("apiUrl")}/products`;
context("GET /products", () => {
  it("gets a list of products", () => {
    cy.request("GET", apiProduct).then((response) => {
      expect(response.status).to.eq(200)
      expect(response.body).length.to.be.greaterThan(15)
    })
  })
})

Comme vous pouvez le voir à la première ligne, je fais d’abord appel à la variable d’environnement apiUrl

Ensuite, les deux lignes expect représentent les tests proprement dits : 

1. Le premier expect vous permet de tester le statut de la réponse. Vous vous attendez à ce que la requête se passe bien, donc que le statut soit 200. 

2. Le second expect vous permet de valider le contenu de la réponse. Dans cet exemple, vous vérifiez la longueur du corps de la réponse reçue. En exigeant que la longueur soit supérieure à 15, vous vous assurez qu'il y a au moins 16 produits renvoyés par l'appel API. Cela garantit que la requête n'est pas seulement réussie, mais qu'elle a également retourné un ensemble de données significatif. Vous pourriez aussi aller plus loin en vérifiant le contenu exact du JSON renvoyé pour confirmer la fidélité des données retournées par l'appel API.

Retrouvez le détail de la création de ce test API en vidéo :

Je remarque qu’on définit baseUrl globalement alors qu’on définit apiUrl dans une variable d’environnement. Pourquoi ? 

Bien vu !

Nous avons défini baseUrl comme URL de base globale : la plupart des tests E2E visent à simuler le comportement de l'utilisateur final sur votre application. Par conséquent, il est courant de définir une URL de base globale pour tous les tests E2E. Cette URL constitue le point de départ de vos tests, ce qui simplifie la navigation entre les pages et les interactions avec l'application.

Nous avons défini apiUrl comme variable d'environnement : l'URL de l'API (dans notre cas, http://localhost:8081) est susceptible de varier en fonction de l'environnement (par exemple, développement, test, production). En utilisant une variable d'environnement, vous pouvez facilement changer cette URL en fonction de l'environnement dans lequel vos tests s'exécutent, ce qui facilite la configuration de vos tests pour différents scénarios.

Par exemple, si vous avez trois environnements, cela donnera :

const { defineConfig } = require("cypress");


module.exports = defineConfig({
  env: {
    apiUrl: "http://localhost:8081",
    devApiUrl: "http://localhost:8082",
    testApiUrl: "http://localhost:8083"
  },
  e2e: {
    setupNodeEvents(on, config) {
      // implement node event listeners here
    },
    baseUrl: "http://localhost:8080/",


  },
});

L’appel se fera de cette façon :

const devApiUrl = Cypress.env('devApiUrl');
cy.request(devApiUrl + '/endpoint');

Et si je sais que je n’aurai qu’un seul environnement ? 

Il est tout à fait possible de déclarer globalement apiUrl comme vous avez défini baseUrl, ce qui donnerait : 

const { defineConfig } = require("cypress");


module.exports = defineConfig({
  e2e: {
    setupNodeEvents(on, config) {
      // implement node event listeners here
    },
    baseUrl: "http://localhost:8080/",
    apiUrl: "http://localhost:8081"
  },
});

Testez une API qui dépend d’un autre appel API

Maintenant que vous avez fait votre premier test API, voyons le cas d’un appel API dépendant d’une réponse d’un autre appel API, comme http://localhost:8081/products/{id}. Le code sera : 

let categoryId;
const apiCategories = `${Cypress.env("apiUrl")}/categories`;
it("Récupérer toutes les catégories et extraire un ID", () => {
  cy.request("GET", apiCategories).then((response) => {
    categoryId = response.body[Math.floor(Math.random() * response.body.length)].id;
  });
});


it("Récupérer les détails d'une catégorie par ID", () => {
  // Vérifiez que l'ID de catégorie a été extrait avec succès
  expect(categoryId).to.be.a("number");


  cy.request(apiCategories + `/${categoryId}`)
    .its("status")
    .should("eq", 200);
});

D’accord… Mais je veux bien une explication de chaque ligne…

Très bien, voyons cela ensemble :

let categoryId;  : Ici, on déclare une variable categoryId qui sera utilisée pour stocker l'ID de catégorie extrait.

const apiCategories = ${Cypress.env("apiUrl")}/categories;  : Cette ligne crée une constante apiCategories en utilisant l'URL de l'API provenant de la variable d'environnement apiUrl, et ajoute /categories à la fin. Cela forme l'URL complète pour l'endpoint qui récupère toutes les catégories.

cy.request("GET", apiCategories)  : Cette ligne effectue une requête GET à l'URL apiCategories pour récupérer toutes les catégories.

.then((response) => { ... });  : C'est une fonction de rappel qui est exécutée une fois que la requête est terminée avec succès.

categoryId = response.body[Math.floor(Math.random() * response.body.length)].id;  : Cela extrait un ID de catégorie aléatoire à partir du corps de la réponse JSON. Cet ID est ensuite stocké dans la variable categoryId.

expect(categoryId).to.be.a("number");  : Avec cette ligne, on vérifie que categoryId est bien un nombre.

Ensuite, on effectue une nouvelle requête GET à l'URL apiCategories suivie de l'ID de la catégorie précédemment extrait (apiCategories + '/' + categoryId) pour récupérer les détails de la catégorie spécifique.

On vérifie que le statut de la réponse est égal à 200 pour s'assurer que la requête a réussi.

Cela garantit que l'ID de catégorie est généré automatiquement à partir de la première réponse API, ce qui le rend dynamique et ne nécessite pas de spécifier manuellement un ID.

Gérez les statuts de réponse des requêtes API

Penchons-nous à présent sur la gestion des statuts de réponse des requêtes API dans Cypress.

L'option failOnStatusCode est une option de configuration spécifique à Cypress. Elle permet de contrôler le comportement des assertions en cas d'échec du code de statut HTTP dans une requête. Cette option est couramment utilisée pour gérer le comportement attendu lorsque la requête renvoie un code de statut non réussi, tel qu'une erreur HTTP 404 (Ressource non trouvée) ou 500 (Erreur interne du serveur).

  • Quand failOnStatusCode est défini sur true (valeur par défaut), Cypress considère toute réponse HTTP hors des codes 2xx (succès) et 3xx (redirection) comme un échec du test. Ainsi, des codes comme 401, 404 ou 500 entraîneront l'échec du test.

  • Quand failOnStatusCode est défini sur false, Cypress ne considère pas automatiquement un code de statut non réussi comme un échec du test. Il vous laisse gérer l'échec avec des assertions Cypress, comme should ou expect, vous permettant de décider de la réaction face à la réponse.

Par exemple, vous vous attendez à avoir une erreur 401 car vous n’êtes pas authentifié :

cy.request({
  method: "POST",
  url: apiUrl + "/reviews",
  failOnStatusCode: false //n’oubliez pas cette option, sinon vous n’irez pas dans la réponse à la ligne suivante
}).then((response) => {
  expect(response.status).to.eq(401);
});

Bien. Après avoir examiné les tests API avec leurs statuts de réponse, vous allez maintenant découvrir comment vous authentifier à l'API.

Authentifiez-vous à votre API

L’API du projet fil rouge Tech&Buy est assez simple parce que vous n’avez pas besoin de vous authentifier pour faire les requêtes. Mais dans des cas un peu plus complexes, par exemple pour pouvoir faire des achats ou aller voir un panier de produits, vous allez avoir besoin de vous authentifier à votre API.

Tout d’abord, il faut connaître les différents types d’authentification : 

Authentification par jeton (Bearer Token)

Il s’agit là d'une des méthodes les plus courantes. Un jeton d'accès (token) est généré par le serveur d'authentification et est inclus dans les en-têtes de la requête HTTP. Le serveur vérifie ensuite ce jeton pour authentifier l'utilisateur.

Authentification de base (Basic Authentication)

Cette méthode utilise un nom d'utilisateur et un mot de passe pour authentifier l’utilisateur. Les informations d'identification sont encodées en base64 et incluses dans les en-têtes de la requête. Cependant, cette méthode n'est pas considérée comme sécurisée à moins que la connexion ne soit sécurisée par HTTPS.

Authentification par clé API (API Key Authentication)

Les clés API sont des chaînes de caractères générées par le serveur qui sont incluses dans les requêtes API en tant que paramètres ou en-têtes. Elles permettent d'identifier les utilisateurs et de leur autoriser l'accès.

Authentification OAuth

OAuth est un protocole d'authentification et d'autorisation largement utilisé pour sécuriser l'accès aux ressources d'une application. Il permet aux utilisateurs de donner leur consentement pour que des applications tierces accèdent à leurs données sans partager leurs informations d'identification.

Authentification par certificat client (Client Certificate Authentication)

Cette méthode utilise des certificats numériques pour authentifier les clients. Le client envoie un certificat au serveur, qui vérifie la validité du certificat pour autoriser l'accès.

Authentification HMAC (Hash-based Message Authentication Code)

Cette méthode implique la création d'un HMAC (code d'authentification basé sur le hachage) à partir des données de la requête et d'une clé secrète partagée. Le serveur valide le HMAC pour authentifier la requête.

Authentification JWT (JSON Web Token)

Les JWT sont des jetons auto-contenus qui contiennent des informations d'identification et sont signés numériquement. Ils sont couramment utilisés pour authentifier les utilisateurs et les services.

Authentification SAML (Security Assertion Markup Language)

SAML est un protocole d'authentification et d'autorisation utilisé pour l'authentification unique (SSO) entre différents systèmes. Il permet aux utilisateurs de s'authentifier une seule fois pour accéder à plusieurs services.

À l’aide du Swagger, vous pouvez avoir le type d’authentification en cliquant sur le bouton Authorize.

Voici un type par exemple :

Capture d'écran montrant les types d'authentification disponibles dans Swagger.
Authentification dans Swagger

Pour vous authentifier, il faut un utilisateur déjà enregistré et autorisé. C’est là que Faker peut vous aider !  

Dans le cas de l’authentification par jeton, vous allez sauvegarder votre jeton (token) dans le hook before, ce qui vous permet d’être connecté avant chaque test, par exemple : 

import { faker } from '@faker-js/faker'; //n’oubliez jamais l’import, qui est nécessaire


const fakeEmail = faker.internet.email();
const fakePassword = faker.internet.password({ length: 20 });


before(() => {
      cy.request("POST", apiUrl + "/login", {
            "username": fakeEmail,
            "password": fakePassword
      }).then((response) => {
            token = response.body.token;
            // Stockez le token dans la variable
      });
});

Vous pouvez ensuite effectuer vos tests :

it("Votre premier test", () => {
  // Utilisez le token dans votre premier test
  cy.request({
    method: "GET",
    url: apiUrl + "/products",
    headers: {
      "Authorization": "Bearer " + token // Utilisez le token ici
    },
    body: {
      //s’il y a un body
    }
  }).then((response) => {
    // Vos assertions pour votre test
    expect(response.status).to.eq(200);
    expect(response.body).to.have.lengthOf(8);
  });
});

Alors que les tests API vous permettent de vérifier les réponses globales des serveurs, dans le prochain chapitre vous allez explorer comment intercepter et contrôler activement les requêtes et les réponses réseau.

Interceptez les requêtes réseau

Pour intercepter et contrôler les requêtes réseau effectuées sur l’application que vous testez, Cypress dispose de la méthode cy.intercept(). Elle permet de simuler des réponses de serveur, de modifier le comportement des requêtes et de tester l'application dans différents scénarios sans dépendre d'un véritable backend.

cy.intercept() doit être utilisé dans un bloc de test it ou un bloc de contexte describe afin de définir l'interception pour des tests spécifiques. Plusieurs interceptions peuvent être définies dans un même test pour différents comportements ou requêtes. 

cy.intercept() agit comme un proxy réseau qui écoute les requêtes et peut agir en tant que serveur pour y répondre ou les modifier avant qu’elles atteignent leur destination réelle. Il peut simuler des réponses HTTP, modifier ou bloquer des requêtes, et vérifier si une requête a été effectuée. C’est particulièrement utile pour tester des fonctionnalités qui dépendent d’appels réseau, comme l’authentification, la gestion des erreurs HTTP, mais il ne peut pas espionner les fonctions JavaScript. 

Mais alors, quelle est la différence entre cy.intercept() et cy.stub() que nous avons vu précédemment ?

cy.stub() est principalement utilisé pour espionner des fonctions JavaScript, et ne peut pas intercepter directement les requêtes réseau effectuées par votre application. Il s’agit davantage d’une technique pour surveiller l’appel et le comportement des fonctions dans votre code. C’est utile lorsque vous souhaitez espionner les appels de fonctions internes, vérifier si elles ont été appelées avec les bons arguments, et éventuellement simuler leur comportement en renvoyant des valeurs spécifiques. cy.stub() ne fonctionne pas avec les requêtes HTTP externes, contrairement à cy.intercept() qui est spécifiquement conçu pour intercepter et contrôler les requêtes HTTP effectuées par votre application. 

La syntaxe de base de cy.intercept() est la suivante :  cy.intercept(routeMatcher, responseHandler)

Ici, routeMatcher est un objet ou une chaîne de caractères qui définit les requêtes à intercepter. Cela peut être une URL complète ou une partie de l'URL.

responseHandler détermine comment gérer les réponses des requêtes interceptées par cy.intercept(). Il peut s’agir :

1. D’un objet, définissant la réponse à renvoyer, comme le statut de la réponse, les en-têtes et le corps (données) de la réponse :

cy.intercept('GET', '/api/users', {
  statusCode: 200,
  body: [{ id: 1, name: 'John' }, { id: 2, name: 'Jane' }],
}).as('getUsers');

2. D’une fonction, qui génère dynamiquement la réponse selon la requête interceptée, comme par exemple :

cy.intercept('POST', '/api/users', (req) => {
  req.reply({ statusCode: 201, body: { id: 3, name: 'New User' } });
}).as('createUser');

Comme avec d'autres commandes Cypress, vous pouvez utiliser .as() pour donner un alias à l'interception et l'utiliser plus tard dans les tests pour des validations ou des attentes.

cy.intercept('GET', '/api/users', { statusCode: 200, body: [] }).as('emptyUsers');
cy.wait('@emptyUsers').then((interception) => {
  // Effectuez des assertions ou des validations ici
});

Une fois que vous avez intercepté une requête, vous pouvez contrôler la réponse à renvoyer, simulant ainsi différents scénarios de réseau, des erreurs, etc.

Vous pouvez également attendre la fin du cycle requête/réponse, par exemple :

cy.intercept({
  url: 'http://example.com/search*',
  query: { q: 'expected terms' },
}).as('search')
cy.wait('@search')

Allez plus loin en créant des commandes personnalisées

Afin de ne pas répéter le code que vous avez écrit, et pouvoir le réutiliser, il existe des commandes personnalisées.

Dans nos exemples, nous répétons souvent le data-cy pour localiser un élément. Cette action peut être extraite afin d’éviter les répétitions.  

Pour commencer, ouvrez le fichier commands.js qui se trouve dans le dossier support que Cypress a créé lors de l’exécution de votre premier test d'exemple. Vous ajoutez ce code : 

Cypress.Commands.add("getBySel", (selector, ...args) => {
  return cy.get(`[data-cy=${selector}]`, ...args)
})

Ainsi, cette fonction vous permet de mettre à jour les exemples que vous avez créés précédemment, à savoir :

describe('contact page', () => {
  it('it navigate to the contact page', () => {
    cy.visit('http://localhost:8080/#/contact')
    cy.getBySel('name').type('name')
    cy.getBySel('email').type('test@test.fr')
    cy.getBySel('message').type('Bonjour, votre site est génial !')
    cy.getBySel('sendButton').click()
    cy.getBySel('successMessage').should('be.visible').should('contain', 'Message envoyé avec succès.')
  })


  it('it send an empty form', () => {
    cy.visit('http://localhost:8080/#/contact')
    cy.getBySel('sendButton').click()
    cy.getBySel('name').should('be.visible').should('have.class', 'ng-invalid')
    cy.getBySel('email').should('have.class', 'ng-invalid')
    cy.getBySel('message').should('have.class', 'ng-invalid')
    cy.getBySel('successMessage').should('not.exist')
  })


  it('it did not contain XSS vulnerability', () => {
    cy.visit('http://localhost:8080/#/contact')
    cy.getBySel('name').type('name')
    cy.getBySel('email').type('test@test.fr')
    cy.getBySel('message').type('<script>alert("XSS");</script>')
    cy.getBySel('sendButton').click()
    cy.getBySel('successMessage').should('be.visible').should('contain', 'Message envoyé avec succès.')
    cy.on('window:alert', () => {
      throw new Error('Une fenêtre d\'alerte s\'est affichée !');
    });
  })
})

Retrouvez le détail de la création de commandes personnalisées en vidéo :

Vous trouverez d’autres commandes sur le site de Cypress. Vous pouvez très bien créer une commande qui vous permet de vous logger sur http://localhost:8080/#/order avec différents utilisateurs en utilisant le faker.js vu dans le chapitre Préparez votre environnement de test

À vous de jouer !

Graphique de bannière À vous de jouer

Pour notre exemple Tech&Buy, rendez-vous sur le swagger http://localhost:8081 afin de voir l’appel API categories et de prendre note du statut et de la réponse du body.

Capture d'écran montrant le statut de la réponse de l'appel API dans Swagger
Statut de la réponse de l'appel API

Créez le test pour l’appel API des catégories.

Consultez la suggestion de corrigé.

En résumé

  • Vous pouvez tester les API avec Cypress. 

  • Pour vous authentifier à l’API, vous aurez besoin d’utiliser des tokens ou de donner des informations de base, une clé API, OAuth, certificats client, HMAC, JWT ou SAML. 

  • Vous pouvez vous aider de la documentation Swagger afin de faire vos tests API.

  • failOnStatusCode est une fonction utile pour tester un statut d’API autre que 2xx ou 3xx.

  • La méthode cy.intercept() de Cypress permet d'intercepter et de contrôler les requêtes réseau effectuées lors des tests, permettant de simuler des réponses de serveur et de tester l'application dans différents scénarios sans backend réel.

  • Vous pouvez créer des commandes afin de ne pas répéter votre code, ce qui permet une meilleure maintenance du code.        

Vous savez donc créer vos tests. Voyons maintenant en détail l’exécution de tous les tests que vous avez créés. 

Exemple de certificat de réussite
Exemple de certificat de réussite