Test backend APIs with Jest
In this guide, we’ll show you how to test backend APIs with Jest. 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 sectionInstallation for new projects
Link to this section-
Install Jest and dotenv:
Terminal window npm install --save-dev jest dotenv -
Create a new
.envfile in your project root:Terminal window touch .env -
Update your
.envfile with your test application credentials from Kinde. You can obtain the refresh token by following this step..env KINDE_CLIENT_ID=your_client_idKINDE_CLIENT_SECRET=your_client_secretKINDE_ISSUER_URL=https://your_kinde_domain.kinde.comTEST_APP_URL=http://localhost:3000TEST_USER_EMAIL=your_test_user_emailKINDE_REFRESH_TOKEN=your-kinde-refresh-token -
Add the
.envfile and.auth/directory to your.gitignorefile:Terminal window touch .gitignoreecho ".env" >> .gitignoreecho ".auth/" >> .gitignore
Configure Kinde refresh tokens
Link to this section-
Create or update your
jest.global-setup.jsfile:Terminal window touch jest.global-setup.js -
Open
jest.global-setup.jsand add the following code to refresh tokens before tests run:jest.global-setup.js require('dotenv').config()const { writeFileSync, mkdirSync, existsSync, readFileSync } = require('fs')const { join, dirname } = require('path')async function refreshTokens() {const tokenUrl = `${process.env.KINDE_ISSUER_URL}/oauth2/token`const tokenPath = join(__dirname, '.auth/api-token.json')const tokenDir = dirname(tokenPath)// Ensure directory existsmkdirSync(tokenDir, { recursive: true })try {// Try to read existing refresh token from file, fallback to envlet refreshToken = process.env.KINDE_REFRESH_TOKENif (existsSync(tokenPath)) {const tokenData = JSON.parse(readFileSync(tokenPath, 'utf-8'))refreshToken = tokenData.refresh_token || refreshToken}const requestBody = new URLSearchParams({grant_type: 'refresh_token',client_id: process.env.KINDE_CLIENT_ID,client_secret: process.env.KINDE_CLIENT_SECRET,refresh_token: refreshToken,}).toString()const response = await fetch(tokenUrl, {method: 'POST',headers: {'Content-Type': 'application/x-www-form-urlencoded',},body: requestBody,})if (!response.ok) {const errorText = await response.text()throw new Error(`Token refresh failed: ${response.status} ${response.statusText} - ${errorText}`)}const tokens = await response.json()// Store tokens in a filewriteFileSync(tokenPath,JSON.stringify({access_token: tokens.access_token,refresh_token: tokens.refresh_token,expires_at: Date.now() + tokens.expires_in * 1000,},null,2))console.log('✓ Tokens refreshed successfully')} catch (error) {console.warn('⚠ Failed to refresh tokens:', error.message)console.warn('⚠ Continuing with existing tokens from file or .env')// Don't throw - allow tests to run with existing tokens}}module.exports = refreshTokens -
Create or update your
package.jsonfile to configure Jest:package.json {"scripts": {"test": "jest"},"jest": {"globalSetup": "<rootDir>/jest.global-setup.js","testEnvironment": "node"}}
Create and run tests
Link to this sectionHere is a sample test file you can run using the Kinde starter test application. Learn how to set up backend API validation with Kinde.
-
Create a test file:
Terminal window touch kinde-api.test.js -
Open
kinde-api.test.jsand add the following example tests:kinde-api.test.js require('dotenv').config()const { readFileSync } = require('fs')const { join } = require('path')// Load access token from file created by jest.global-setup.js// Load access token from file created by jest.global-setup.jsconst tokenPath = join(__dirname, '.auth/api-token.json')const tokenData = JSON.parse(readFileSync(tokenPath, 'utf-8'))let accessTokentry {const tokenData = JSON.parse(readFileSync(tokenPath, 'utf-8'))accessToken = tokenData.access_token} catch (error) {throw new Error(`Token file not found at ${tokenPath}. Run token refresh first or check KINDE_REFRESH_TOKEN.`)}const testAppUrl = process.env.TEST_APP_URLdescribe('Kinde API Tests', () => {test('can fetch user profile', async () => {const response = await fetch(`${testAppUrl}/api/profile`, {headers: {Authorization: `Bearer ${accessToken}`,},})expect(response.ok).toBeTruthy()const data = await response.json()expect(data.email).toBe(process.env.TEST_USER_EMAIL)})test('cannot fetch user profile with invalid token', async () => {const response = await fetch(`${testAppUrl}/api/profile`, {headers: {Authorization: `Bearer invalid-token`,},})expect(response.status).toBe(401)})test('can update user settings', async () => {const response = await fetch(`${testAppUrl}/api/settings`, {method: 'PATCH',headers: {Authorization: `Bearer ${accessToken}`,},body: JSON.stringify({theme: 'dark',}),})expect(response.ok).toBeTruthy()})}) -
Run the tests using the following command:
Terminal window npm testYou should see the tests pass.
Terminal window PASS ./kinde-api.test.jsKinde API Tests✓ can fetch user profile (904 ms)✓ cannot fetch user profile with invalid token (185 ms)✓ can update user settings (452 ms)Test Suites: 1 passed, 1 totalTests: 3 passed, 3 totalSnapshots: 0 totalTime: 1.683 sRan all test suites.
You have successfully run the tests and validated that the API is working correctly. Use this approach to test your API endpoints and ensure they function as expected.
CI/CD integration
Link to this sectionFor CI/CD environments, use environment variables instead of .env:
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 Jest API tests run: npm test env: KINDE_ISSUER_URL: ${{ secrets.KINDE_ISSUER_URL }} KINDE_CLIENT_ID: ${{ secrets.KINDE_CLIENT_ID }} KINDE_CLIENT_SECRET: ${{ secrets.KINDE_CLIENT_SECRET }} KINDE_REFRESH_TOKEN: ${{ secrets.KINDE_REFRESH_TOKEN }} TEST_APP_URL: ${{ secrets.TEST_APP_URL }} TEST_USER_EMAIL: ${{ secrets.TEST_USER_EMAIL }}