Skip to content
  • Testing
  • Cypress

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.

Configure Kinde refresh tokens

Link to this section
  1. Update your cypress.env.json file with the refresh token. You can obtain the refresh token by following this step.

    {
    // ...your other environment variables...
    "KINDE_REFRESH_TOKEN": "your-kinde-refresh-token",
    }
  2. Create an API setup file:

    Terminal window
    mkdir -p cypress/e2e/api
    touch cypress/e2e/api/api.setup.js
  3. 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'),
    })
    })
    })
  4. Update your cypress.config.js file 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 file
    const tokenPath = join(__dirname, 'cypress/.auth/api-token.json')
    const tokenDir = dirname(tokenPath)
    // Ensure directory exists
    mkdirSync(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_token
    return 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 refresh
    return null
    }
    throw error
    }
    },
    })
    },
    },
    })

    This will save the tokens to a file in the .auth directory.

  5. Add the .auth directory to your .gitignore file:

    Terminal window
    echo ".auth/" >> .gitignore

Create and run sample tests

Link to this section

If you are starting from scratch, you can run sample tests using the Kinde starter test application.

  1. Create a test file:

    Terminal window
    touch cypress/e2e/api/kinde-api.cy.js
  2. Open kinde-api.cy.js and add the following example tests:

    cypress/e2e/api/kinde-api.cy.js
    describe('Kinde API Tests', () => {
    let accessToken
    before(() => {
    // Load access token from file, or refresh if file doesn't exist
    cy.task('readApiToken').then((tokenData) => {
    if (!tokenData) {
    // Token file doesn't exist, refresh tokens first
    return 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)
    })
    })
    })
  3. Run the tests using the following command:

    Terminal window
    npx cypress open

    You should see the tests pass.

    Cypress API tests passed

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 section

For CI/CD environments, use environment variables instead of cypress.env.json:

.github/workflows/e2e-tests.yml
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 }}