Test backend APIs
In this guide, we’ll show you how to test backend APIs with Cypress. We will use Kinde refresh tokens for maintaining authenticated sessions in automated tests, including token rotation. Learn more about testing backend APIs with Kinde.
What you need
Link to this section- Completed the Cypress setup
- A test user and a running Kinde application.
Configure Kinde refresh tokens
Link to this section-
Update your
cypress.env.jsonfile with therefresh token. You can obtain the refresh token by following this step.{// ...your other environment variables..."KINDE_REFRESH_TOKEN": "your-kinde-refresh-token",}Important: Make sure to add
cypress.env.jsonto your.gitignorefile to prevent committing tokens to source control. -
Create an API setup file:
Terminal window mkdir -p cypress/e2e/apitouch cypress/e2e/api/api.setup.js -
Add the following to the file to refresh tokens before tests run:
cypress/e2e/api/api.setup.js describe('API Setup', () => {it('refresh API tokens', () => {cy.task('refreshApiTokens', {issuerUrl: Cypress.env('KINDE_ISSUER_URL'),clientId: Cypress.env('KINDE_CLIENT_ID'),refreshToken: Cypress.env('KINDE_REFRESH_TOKEN'),})})}) -
Update your
cypress.config.jsfile with the new task:cypress.config.js const { defineConfig } = require('cypress')const { writeFileSync, mkdirSync, readFileSync } = require('fs')const { join, dirname } = require('path')module.exports = defineConfig({e2e: {baseUrl: 'http://localhost:3000',setupNodeEvents(on, config) {on('task', {async refreshApiTokens({ issuerUrl, clientId, refreshToken }) {const response = await fetch(`${issuerUrl}/oauth2/token`, {method: 'POST',headers: {'Content-Type': 'application/x-www-form-urlencoded',},body: new URLSearchParams({grant_type: 'refresh_token',client_id: clientId,refresh_token: refreshToken,}),})if (!response.ok) {throw new Error(`Failed to refresh token: ${response.statusText}`)}const tokens = await response.json()// Store access token in a fileconst tokenPath = join(__dirname, 'cypress/.auth/api-token.json')const tokenDir = dirname(tokenPath)// Ensure directory existsmkdirSync(tokenDir, { recursive: true })writeFileSync(tokenPath,JSON.stringify({access_token: tokens.access_token,refresh_token: tokens.refresh_token,expires_at: Date.now() + tokens.expires_in * 1000,},null,2))// Important: Update your secrets manager with the new refresh token// for subsequent test runs// const newRefreshToken = tokens.refresh_tokenreturn null},readApiToken() {const tokenPath = join(__dirname, 'cypress/.auth/api-token.json')try {const tokenData = JSON.parse(readFileSync(tokenPath, 'utf-8'))return tokenData} catch (error) {if (error.code === 'ENOENT') {// File doesn't exist, return null to trigger refreshreturn null}throw error}},})},},})This will save the tokens to a file in the
.authdirectory. -
Add the
.authdirectory to your.gitignorefile:Terminal window echo ".auth/" >> .gitignore
Create and run sample tests
Link to this sectionIf you are starting from scratch, you can run sample tests using the Kinde starter test application.
-
Create a test file:
Terminal window touch cypress/e2e/api/kinde-api.cy.js -
Open
kinde-api.cy.jsand add the following example tests:cypress/e2e/api/kinde-api.cy.js describe('Kinde API Tests', () => {let accessTokenbefore(() => {// Load access token from file, or refresh if file doesn't existcy.task('readApiToken').then((tokenData) => {if (!tokenData) {// Token file doesn't exist, refresh tokens firstreturn cy.task('refreshApiTokens', {issuerUrl: Cypress.env('KINDE_ISSUER_URL'),clientId: Cypress.env('KINDE_CLIENT_ID'),refreshToken: Cypress.env('KINDE_REFRESH_TOKEN'),}).then(() => {return cy.task('readApiToken')})}return tokenData}).then((tokenData) => {accessToken = tokenData.access_token})})it('can fetch user profile', () => {cy.request({url: `${Cypress.env('TEST_APP_URL')}/api/profile`,headers: {Authorization: `Bearer ${accessToken}`,},}).then((response) => {expect(response.status).to.eq(200)expect(response.body.email).to.eq(Cypress.env('TEST_USER_EMAIL'))})})it('cannot fetch user profile with invalid token', () => {cy.request({url: `${Cypress.env('TEST_APP_URL')}/api/profile`,headers: {Authorization: `Bearer invalid-token`,},failOnStatusCode: false,}).then((response) => {expect(response.status).to.eq(401)})})it('can update user settings', () => {cy.request({method: 'PATCH',url: `${Cypress.env('TEST_APP_URL')}/api/settings`,headers: {Authorization: `Bearer ${accessToken}`,},body: {theme: 'dark',},}).then((response) => {expect(response.status).to.eq(200)})})}) -
Run the tests using the following command:
Terminal window npx cypress openYou should see the tests pass.
You have successfully run the tests and validated that the API is working as expected. Use this approach to test your API endpoints and ensure they are working as expected.
CI/CD integration
Link to this sectionFor CI/CD environments, use environment variables instead of cypress.env.json:
name: API Tests
on: push: branches: [main]
jobs: test: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: node-version: 20 - name: Install dependencies run: npm ci - name: Run Cypress API tests run: npx cypress run --spec "cypress/e2e/api/**/*.cy.js" env: KINDE_ISSUER_URL: ${{ secrets.KINDE_ISSUER_URL }} KINDE_CLIENT_ID: ${{ secrets.KINDE_CLIENT_ID }} TEST_APP_URL: ${{ secrets.TEST_APP_URL }} TEST_USER_EMAIL: ${{ secrets.TEST_USER_EMAIL }} KINDE_REFRESH_TOKEN: ${{ secrets.KINDE_REFRESH_TOKEN }}