Vous utilisez déjà Cypress pour vos tests de bout en bout. Maintenant, vous voulez interroger une API directement, vérifier le code de statut et faire des assertions sur le corps JSON sans passer par l'interface utilisateur. Cypress peut le faire. La commande cy.request() envoie de vraies requêtes HTTP depuis Node, en dehors du navigateur, vous permettant ainsi de tester un point de terminaison de manière isolée ou de configurer l'état avant l'exécution d'un test d'interface utilisateur.
Ce guide vous montre comment tester des API avec Cypress : les bases de cy.request(), l'assertion sur les réponses, l'enchaînement de requêtes pour réutiliser un jeton d'authentification, et la simulation du trafic navigateur avec cy.intercept(). Vous verrez également quand Cypress est un bon choix pour les API et quand un outil natif pour API est plus approprié.
Pourquoi tester les API avec Cypress
La plupart des suites Cypress pilotent un navigateur. Mais une grande partie de ce que vous vérifiez dans l'interface utilisateur est en fait l'API sous-jacente : est-ce que /login renvoie un jeton, est-ce que /users renvoie la bonne structure, est-ce qu'une requête POST crée l'enregistrement attendu.
Tester ces points de terminaison directement présente deux avantages. Premièrement, les vérifications d'API s'exécutent plus rapidement que les flux d'interface utilisateur car il n'y a pas de rendu, pas d'attente d'éléments. Deuxièmement, vous pouvez utiliser cy.request() pour configurer les données de test. Au lieu de parcourir un formulaire d'inscription pour créer un utilisateur, vous envoyez une seule requête POST et passez à l'assertion qui vous intéresse réellement.
Si Cypress fait déjà partie de votre pile technologique, l'ajout de tests d'API signifie qu'aucun nouvel outil n'est à installer. Vous les écrivez dans les mêmes fichiers de spécification, avec les mêmes assertions expect, lors de la même exécution CI.
Les bases de cy.request()
cy.request() effectue une requête HTTP et renvoie la réponse. Elle s'exécute depuis le processus Node de Cypress, et non depuis le navigateur, elle n'est donc pas soumise aux règles CORS ou de même origine.
La commande accepte plusieurs formes :
cy.request(url)
cy.request(url, body)
cy.request(method, url)
cy.request(method, url, body)
cy.request(options)
L'appel le plus simple passe une URL. Sans méthode, Cypress utilise GET par défaut :
cy.request('https://jsonplaceholder.typicode.com/users')
Pour toute requête au-delà d'un simple GET, passez un objet d'options. Cela vous donne un contrôle total sur la méthode, le corps et les en-têtes :
cy.request({
method: 'POST',
url: 'https://jsonplaceholder.typicode.com/posts',
headers: {
'Content-Type': 'application/json'
},
body: {
title: 'API test',
body: 'created from Cypress',
userId: 1
}
})
Options utiles pour cet objet :
method: le verbe HTTP (GET,POST,PUT,PATCH,DELETEet autres). Par défaut :GET.url: le point de terminaison. Obligatoire.body: la charge utile de la requête. Cypress sérialise les objets en JSON.headers: un objet d'en-têtes de requête.auth: identifiants pour l'authentification HTTP Basic.qs: paramètres de chaîne de requête sous forme d'objet.failOnStatusCode: à définir surfalselorsque vous souhaitez faire une assertion sur une réponse 4xx ou 5xx au lieu de faire échouer le test.
Cette dernière option est importante pour les tests négatifs. Par défaut, cy.request() fait échouer le test pour tout statut non 2xx ou non 3xx. Si vous vérifiez qu'une mauvaise requête renvoie 400, vous devez désactiver cette option :
cy.request({
method: 'GET',
url: 'https://jsonplaceholder.typicode.com/users/99999',
failOnStatusCode: false
}).then((response) => {
expect(response.status).to.eq(404)
})
Assertions sur le statut et le corps de la réponse
cy.request() renvoie un objet de réponse. Chaînez .then() pour le lire. La réponse possède quatre propriétés que vous utiliserez le plus souvent :
status: le code de statut HTTP.body: la charge utile de la réponse. Cypress la parse en un objet JavaScript lorsque leContent-Typese termine parjson.headers: les en-têtes de la réponse.duration: la durée de la requête, en millisecondes.
Voici un test complet qui vérifie le statut, la structure du corps et un champ spécifique :
describe('Users API', () => {
it('returns a list of users', () => {
cy.request('https://jsonplaceholder.typicode.com/users').then((response) => {
expect(response.status).to.eq(200)
expect(response.body).to.be.an('array')
expect(response.body).to.have.length(10)
expect(response.body[0]).to.have.property('email')
})
})
})
Étant donné que response.body est déjà un objet parsé, vous pouvez faire des assertions dessus avec Chai standard. Vous pouvez vérifier le type, la longueur, les propriétés imbriquées ou les valeurs exactes. C'est la même syntaxe d'assertion que celle que vous utilisez pour les tests d'interface utilisateur, il n'y a donc rien de nouveau à apprendre.
Vous pouvez également faire des assertions sur les en-têtes de réponse, ce qui est pratique pour vérifier le type de contenu ou la mise en cache :
cy.request('https://jsonplaceholder.typicode.com/users').then((response) => {
expect(response.headers['content-type']).to.include('application/json')
})
Pour une étude plus approfondie de la structuration de ces vérifications, consultez notre guide sur les assertions d'API et les plus larges meilleures pratiques de test d'API.
Enchaîner les requêtes et réutiliser un jeton d'authentification
Les API réelles nécessitent une authentification. Le schéma courant est de se connecter une fois, de récupérer le jeton, puis de l'envoyer avec chaque requête subséquente. cy.request() s'enchaîne de manière fluide pour cela.
Envoyez la requête de connexion, lisez le jeton du corps de la réponse, puis utilisez-le dans l'appel suivant :
describe('Authenticated API flow', () => {
it('logs in and fetches a protected resource', () => {
cy.request({
method: 'POST',
url: 'https://api.example.com/login',
body: {
email: 'user@example.com',
password: 'secret'
}
}).then((loginResponse) => {
expect(loginResponse.status).to.eq(200)
const token = loginResponse.body.token
cy.request({
method: 'GET',
url: 'https://api.example.com/profile',
headers: {
Authorization: `Bearer ${token}`
}
}).then((profileResponse) => {
expect(profileResponse.status).to.eq(200)
expect(profileResponse.body).to.have.property('email', 'user@example.com')
})
})
})
})
Si plusieurs tests ont besoin du même jeton, déplacez la connexion dans un beforeEach et stockez le jeton avec Cypress.env() ou un alias enveloppé afin que chaque test puisse le récupérer. Cela permet de centraliser la configuration de l'authentification au lieu de la répéter dans chaque spécification.
Ce modèle de connexion puis d'utilisation est le même que celui que vous écririez dans tout flux de tests d'intégration d'API, où un appel alimente le suivant en données.
cy.intercept() pour la simulation
cy.request() interroge la véritable API. Parfois, vous voulez l'inverse : intercepter les requêtes faites par votre application front-end et renvoyer une fausse réponse. C'est le rôle de cy.intercept().
Il y a une distinction importante ici. cy.intercept() n'intercepte que les requêtes faites par votre application front-end dans le navigateur. Il n'intercepte pas cy.request(), car cy.request() contourne entièrement le navigateur. Utilisez cy.intercept() lorsque vous testez la façon dont votre interface utilisateur réagit à une réponse API donnée, et non lorsque vous testez l'API elle-même.
Vous pouvez espionner une requête, la simuler avec une réponse statique, ou l'attendre. Pour simuler, passez un objet de réponse :
cy.intercept('GET', '/api/users', {
statusCode: 200,
body: [{ id: 1, name: 'Ada' }]
})
Désormais, toute requête de navigateur vers /api/users renvoie cette charge utile fixe. Cela vous permet de tester des cas limites difficiles à déclencher sur un backend en production, comme une liste vide, une erreur 500, ou une réponse lente.
Pour affirmer que la requête a eu lieu, aliassez-la avec .as() et attendez-la :
it('shows an error banner when the API fails', () => {
cy.intercept('GET', '/api/users', {
statusCode: 500,
body: { message: 'Server error' }
}).as('getUsers')
cy.visit('/dashboard')
cy.wait('@getUsers').its('response.statusCode').should('eq', 500)
cy.contains('Something went wrong').should('be.visible')
})
La ligne cy.wait('@getUsers') met le test en pause jusqu'à ce que la requête interceptée soit résolue, puis vous fournit la requête et la réponse pour faire des assertions. Configurez l'interception avant l'action qui déclenche la requête, sinon elle ne capturera rien. Si vous hésitez entre simuler une réponse et mocker un service entier, nos articles sur le mocking d'API et la simulation d'API vs mocking d'API détaillent les compromis.
Quand Cypress est pertinent pour les API, et quand il ne l'est pas
Cypress est un excellent choix pour les vérifications d'API qui s'inscrivent dans une suite de tests d'interface utilisateur. Si vous écrivez déjà des tests de bout en bout et que vous avez besoin d'amorcer des données, de vérifier un point de terminaison dont votre interface utilisateur dépend, ou de simuler une réponse pour tester la gestion des erreurs, cy.request() et cy.intercept() centralisent tout. Pas de changement de contexte, pas de deuxième outil.
C'est également approprié pour quelques tests de fumée API autonomes lorsque Cypress est votre seul exécutant de tests et que vous ne voulez pas ajouter une autre dépendance.
Cela devient moins pratique pour une suite de tests API volumineuse et autonome. Cypress a été conçu pour le navigateur, le test d'API est donc une capacité ajoutée par-dessus, et non la conception fondamentale. Quelques lacunes apparaissent à mesure que la suite s'agrandit :
- Pas de constructeur de requêtes visuel. Chaque requête est du code. Composer les en-têtes, les corps et les paramètres de requête à la main devient lent lorsque vous avez des centaines de points de terminaison.
- Moins efficace pour un pipeline API uniquement CI. Cypress démarre un contexte de navigateur même pour les exécutions purement API, ce qui est une surcharge inutile.
- Pas de définition d'API partagée. Les structures des requêtes se trouvent dans vos fichiers de test, et non dans une spécification que votre équipe peut réutiliser pour la conception, la documentation et les tests.
C'est là qu'un outil natif pour API est plus approprié. Apidog est conçu autour de l'API elle-même : vous concevez un point de terminaison une seule fois, puis construisez des scénarios de test contre celui-ci avec un constructeur de requêtes visuel et des assertions visuelles, sans nécessiter de code pour les cas courants. Les requêtes, les environnements et l'authentification résident dans un espace de travail partagé, ainsi votre équipe n'a pas à les redériver à partir des fichiers de test.
Pour l'intégration continue (CI), l'interface en ligne de commande (CLI) d'Apidog exécute vos scénarios de test enregistrés en mode sans affichage dans toute étape pouvant exécuter Node :
npm install -g apidog-cli
apidog run \
--access-token "$APIDOG_ACCESS_TOKEN" \
-t <scenarioOrSuiteId> \
-e <environmentId> \
-r cli,html,junit
L'option -t cible un scénario ou une suite enregistrée, -e sélectionne un environnement, et -r choisit les formats de rapport (cli, html, json, junit). La sortie JUnit s'intègre directement dans la plupart des tableaux de bord CI. Ajoutez -d ou --iteration-data pour exécuter un test à partir d'un fichier de données, et --upload-report pour envoyer les résultats à votre espace de travail. La CLI exécute des scénarios enregistrés ; ce n'est pas un expéditeur de requêtes interactif.
Une façon utile de voir les choses : utilisez cy.request() pour les vérifications d'API qui supportent vos tests de navigateur, et optez pour un outil natif pour API lorsque le test d'API est la tâche principale. Si vous comparez les options, nos récapitulatifs des outils d'automatisation des tests API qui s'exécutent en CI/CD et des plus larges 30 meilleurs outils de test API couvrent le domaine.
FAQ
Est-ce que cy.request() s'exécute dans le navigateur ?
Non. cy.request() s'exécute depuis le processus Node de Cypress, en dehors du navigateur. C'est pourquoi elle n'est pas bloquée par la politique CORS ou de même origine, et pourquoi cy.intercept() ne peut pas l'intercepter. Si vous avez besoin d'une requête effectuée par le navigateur, déclenchez-la via votre application et capturez-la avec cy.intercept().
Comment tester une réponse 400 ou 404 sans faire échouer le test ?
Par défaut, cy.request() échoue pour tout statut non 2xx ou non 3xx. Définissez failOnStatusCode: false dans l'objet d'options, puis faites vous-même une assertion sur le statut avec expect(response.status).to.eq(404).
Puis-je utiliser cy.request() pour me connecter avant un test d'interface utilisateur ?
Oui, et c'est un schéma courant. Envoyez une requête POST à votre point de terminaison de connexion avec cy.request(), lisez le jeton depuis response.body, et stockez-le (dans Cypress.env(), localStorage, ou un cookie) afin que votre test d'interface utilisateur démarre déjà authentifié. Cela contourne le formulaire de connexion et accélère la suite.
Quelle est la différence entre cy.request() et cy.intercept() ?
cy.request() envoie une vraie requête HTTP et renvoie la réponse réelle, vous l'utilisez donc pour tester une API. cy.intercept() surveille ou simule les requêtes que votre front-end effectue dans le navigateur, vous l'utilisez donc pour contrôler ce que votre interface utilisateur reçoit. L'une interroge le réseau ; l'autre le façonne.
Devrais-je utiliser Cypress ou un outil dédié pour les tests d'API ?
Utilisez Cypress lorsque les vérifications d'API supportent une suite de tests de navigateur que vous exécutez déjà. Pour une suite API autonome volumineuse, des pipelines API uniquement CI, ou une définition d'API partagée à partir de laquelle toute votre équipe travaille, un outil natif pour API comme Apidog est plus adapté. Consultez nos bonnes pratiques de test d'API pour savoir comment décider.
