Automating API Testing with Cypress

Automating API Testing with Cypress
Automating API testing with Cypress. Step-by-step guide to API test automation for reliable backend coverage.

Automating API testing with Cypress - practical guide with examples

cy.request() Core API method
Chai Built-in assertions
CI/CD Ready

Why use Cypress for API testing?

Cypress is primarily known as an end-to-end UI testing tool, but its cy.request() command makes it a solid choice for API testing too. If your team already uses Cypress for frontend tests, adding API tests to the same framework avoids the overhead of maintaining a separate tool.

Advantages of Cypress for API testing (v15+):

  • Tests run in the same framework as your E2E tests
  • Built-in retry logic and assertions via Chai
  • Easy integration with CI/CD pipelines
  • Network request interception with cy.intercept() for mocking
  • Automatic cookie and session management
  • Cypress Studio included by default for recording tests

Basic API test with cy.request()

Here is a simple test that hits a REST API and validates the response:

users.cy.js
describe('Users API', () => {
  it('returns a list of users', () => {
    cy.request('GET', '/api/users').then((response) => {
      expect(response.status).to.eq(200);
      expect(response.body).to.be.an('array');
      expect(response.body.length).to.be.greaterThan(0);
      expect(response.body[0]).to.have.property('id');
      expect(response.body[0]).to.have.property('email');
    });
  });

  it('creates a new user', () => {
    cy.request({
      method: 'POST',
      url: '/api/users',
      body: {
        name: 'Test User',
        email: '[email protected]',
      },
    }).then((response) => {
      expect(response.status).to.eq(201);
      expect(response.body.name).to.eq('Test User');
      expect(response.body.id).to.be.a('number');
    });
  });
});

Testing with authentication

Bearer token authentication

auth.cy.js
describe('Protected API', () => {
  let authToken;

  before(() => {
    cy.request({
      method: 'POST',
      url: '/api/auth/login',
      body: {
        email: Cypress.env('TEST_USER_EMAIL'),
        password: Cypress.env('TEST_USER_PASSWORD'),
      },
    }).then((response) => {
      authToken = response.body.token;
    });
  });

  it('accesses protected resource', () => {
    cy.request({
      method: 'GET',
      url: '/api/profile',
      headers: {
        Authorization: `Bearer ${authToken}`,
      },
    }).then((response) => {
      expect(response.status).to.eq(200);
      expect(response.body.email).to.eq(Cypress.env('TEST_USER_EMAIL'));
    });
  });

  it('rejects requests without token', () => {
    cy.request({
      method: 'GET',
      url: '/api/profile',
      failOnStatusCode: false,
    }).then((response) => {
      expect(response.status).to.eq(401);
    });
  });
});
Tip: Store credentials in cypress.env.json (add it to .gitignore) or pass them as environment variables in CI.

Validating response schemas

As your API evolves, response structures can change unexpectedly. Schema validation catches these regressions:

schema.cy.js
it('user response matches expected schema', () => {
  cy.request('GET', '/api/users/1').then((response) => {
    expect(response.body).to.have.all.keys(
      'id', 'name', 'email', 'created_at', 'role'
    );
    expect(response.body.id).to.be.a('number');
    expect(response.body.name).to.be.a('string');
    expect(response.body.email).to.match(/^[^\s@]+@[^\s@]+\.[^\s@]+$/);
    expect(response.body.role).to.be.oneOf(['admin', 'user', 'editor']);
  });
});

For more complex schema validation, install chai-json-schema and define JSON Schema objects.

Testing error handling

Good API tests cover failure cases too:

errors.cy.js
describe('Error handling', () => {
  it('returns 404 for non-existent resource', () => {
    cy.request({
      method: 'GET',
      url: '/api/users/999999',
      failOnStatusCode: false,
    }).then((response) => {
      expect(response.status).to.eq(404);
      expect(response.body).to.have.property('error');
    });
  });

  it('returns 400 for invalid input', () => {
    cy.request({
      method: 'POST',
      url: '/api/users',
      body: { name: '' },
      failOnStatusCode: false,
    }).then((response) => {
      expect(response.status).to.eq(400);
      expect(response.body.errors).to.be.an('array');
    });
  });
});
Note: Use failOnStatusCode: false when you expect non-2xx responses. Without it, Cypress treats any non-2xx status as a test failure.

Mocking APIs with cy.intercept()

When testing the frontend, you often want to control API responses. cy.intercept() lets you mock, stub, or modify network requests:

mocking.cy.js
it('displays error message when API fails', () => {
  cy.intercept('GET', '/api/users', {
    statusCode: 500,
    body: { error: 'Internal Server Error' },
  }).as('getUsers');

  cy.visit('/users');
  cy.wait('@getUsers');
  cy.contains('Something went wrong').should('be.visible');
});

it('displays empty state when no data', () => {
  cy.intercept('GET', '/api/users', {
    statusCode: 200,
    body: [],
  }).as('getUsers');

  cy.visit('/users');
  cy.wait('@getUsers');
  cy.contains('No users found').should('be.visible');
});

This is useful for testing edge cases that are hard to reproduce with a real backend.

Organizing API tests

For larger test suites, structure your API tests by resource:

cypress/ e2e/ api/ users.cy.js auth.cy.js orders.cy.js payments.cy.js ui/ login.cy.js dashboard.cy.js support/ commands.js # Custom commands for common API calls

Create custom commands for repeated API operations:

cypress/support/commands.js
Cypress.Commands.add('apiLogin', (email, password) => {
  return cy.request({
    method: 'POST',
    url: '/api/auth/login',
    body: { email, password },
  }).then((response) => {
    window.localStorage.setItem('token', response.body.token);
    return response.body.token;
  });
});

// In tests:
cy.apiLogin('[email protected]', 'password123');

Running in CI/CD

Add Cypress API tests to your pipeline. They run fast since they do not need a browser:

GitHub Actions
api-tests:
  runs-on: ubuntu-latest
  steps:
    - uses: actions/checkout@v4
    - run: npm install
    - run: npx cypress run --spec "cypress/e2e/api/**"
      env:
        CYPRESS_BASE_URL: ${{ secrets.API_BASE_URL }}
        CYPRESS_TEST_USER_EMAIL: ${{ secrets.TEST_EMAIL }}
        CYPRESS_TEST_USER_PASSWORD: ${{ secrets.TEST_PASSWORD }}

When to use Cypress vs dedicated API tools

Use Cypress when:

  • Team already uses Cypress for E2E
  • Need API + UI tests in one framework
  • Want to combine API setup with UI assertions

Use Postman/REST Assured when:

  • Need complex assertion chains
  • Testing APIs without frontend
  • Need API documentation alongside tests
  • No Node.js environment available

Both approaches are valid. The best choice depends on your team's existing stack and testing goals.

For teams setting up API testing from scratch, BetterQA's automation engineers can help design a testing strategy that fits your architecture and CI/CD pipeline.

Talk to our team

Need help with software testing?

BetterQA provides independent QA services with 50+ engineers across manual testing, automation, security audits, and performance testing.

Share the Post: