Automating API testing with Cypress - practical guide with examples
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:
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
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);
});
});
});
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:
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:
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');
});
});
});
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:
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.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:
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 teamNeed help with software testing?
BetterQA provides independent QA services with 50+ engineers across manual testing, automation, security audits, and performance testing.