API Testing Overview
API tests in this framework validate REST endpoints using Cypress’s cy.request() command. Unlike UI tests, API tests don’t use page objects - they interact directly with HTTP endpoints.
Basic API Test Structure
describe ( 'API Requests - GET' , () => {
it ( 'Should receive a 200 status response' , () => {
cy . request ({
method: 'GET' ,
url: '/booking' ,
}). its ( 'status' ). should ( 'eq' , 200 )
})
});
API Test Anatomy
Describe Block
Groups related API tests together (e.g., all GET requests).
Request
Uses cy.request() to make HTTP calls to the API.
Assertions
Validates response status, headers, body, and performance.
Complete API Test Example
Here’s a real test suite from the framework:
/// < reference types = "cypress" />
describe ( 'API Requests - GET' , () => {
it ( 'Should receive a 200 status response' , () => {
cy . request ({
method: 'GET' ,
url: '/booking' ,
}). its ( 'status' ). should ( 'eq' , 200 )
})
it ( 'Should last no more than 1 second' , () => {
cy . request ({
method: 'GET' ,
url: '/booking' ,
}). then (( res ) => {
expect ( res . duration ). to . be . lessThan ( 1000 )
})
})
it ( 'Should have the correct headers' , () => {
cy . request ({
method: 'GET' ,
url: '/booking' ,
}). its ( 'headers' ). its ( 'content-type' )
. should ( 'include' , 'application/json' )
})
it ( 'Should return an array' , () => {
cy . request ({
method: 'GET' ,
url: '/booking' ,
}). then (( res ) => {
expect ( res . body ). to . be . an ( 'array' )
})
})
it ( 'Should have a bookingid as property for each one' , () => {
cy . request ({
method: 'GET' ,
url: '/booking' ,
}). then (( res ) => {
res . body . forEach ( item => {
expect ( item ). to . have . property ( 'bookingid' ). that . is . a ( 'number' )
})
})
})
it ( 'Should return at least 1 result' , () => {
cy . request ({
method: 'GET' ,
url: '/booking' ,
}). then (( res ) => {
expect ( res . body ). to . not . be . empty
})
})
});
Request Configuration
The cy.request() command accepts various options:
GET Request
POST Request
With Headers
With Query Parameters
cy . request ({
method: 'GET' ,
url: '/booking/1' ,
})
Request Options
method : HTTP method (GET, POST, PUT, DELETE, PATCH)
url : Endpoint URL (relative to baseUrl in config)
body : Request payload for POST/PUT/PATCH
headers : Custom HTTP headers
qs : Query string parameters
auth : Basic authentication
timeout : Request timeout in milliseconds
Response Validation
Status Code Validation
it ( 'Should return 200 for successful GET' , () => {
cy . request ( 'GET' , '/booking' )
. its ( 'status' )
. should ( 'eq' , 200 )
})
it ( 'Should return 404 for non-existent resource' , () => {
cy . request ({
method: 'GET' ,
url: '/booking/99999' ,
failOnStatusCode: false // Don't fail test on 4xx/5xx
}). its ( 'status' ). should ( 'eq' , 404 )
})
it ( 'Should have correct content-type header' , () => {
cy . request ( '/booking' )
. its ( 'headers' )
. its ( 'content-type' )
. should ( 'include' , 'application/json' )
})
it ( 'Should have CORS headers' , () => {
cy . request ( '/booking' ). then (( res ) => {
expect ( res . headers ). to . have . property ( 'access-control-allow-origin' )
})
})
Body Validation
it ( 'Should return array of bookings' , () => {
cy . request ( '/booking' ). then (( res ) => {
expect ( res . body ). to . be . an ( 'array' )
expect ( res . body ). to . not . be . empty
})
})
it ( 'Should have required properties' , () => {
cy . request ( '/booking/1' ). then (( res ) => {
expect ( res . body ). to . have . property ( 'firstname' )
expect ( res . body ). to . have . property ( 'lastname' )
expect ( res . body ). to . have . property ( 'totalprice' )
expect ( res . body . totalprice ). to . be . a ( 'number' )
})
})
it ( 'Should respond within 1 second' , () => {
cy . request ( '/booking' ). then (( res ) => {
expect ( res . duration ). to . be . lessThan ( 1000 )
})
})
it ( 'Should respond within 500ms for cached data' , () => {
cy . request ({
method: 'GET' ,
url: '/booking/1' ,
headers: {
'Cache-Control' : 'max-age=3600'
}
}). then (( res ) => {
expect ( res . duration ). to . be . lessThan ( 500 )
})
})
The duration property in the response shows how long the request took in milliseconds.
Testing with Fixtures
Use fixtures to validate API responses against expected data:
[
{
"firstname" : "James" ,
"lastname" : "Brown" ,
"totalprice" : 111 ,
"depositpaid" : true ,
"bookingdates" : {
"checkin" : "2018-01-01" ,
"checkout" : "2019-01-01"
},
"additionalneeds" : "Breakfast"
}
]
Using in tests:
it ( 'Should return specific booking data' , () => {
cy . fixture ( 'bookings' ). then (( bookingData : any ) => {
for ( let i = 1 ; i < 4 ; i ++ ) {
cy . request ({
method: 'GET' ,
url: `/booking/ ${ i } ` ,
}). then (( res ) => {
const { firstname , lastname , totalprice , depositpaid } = res . body
expect ( firstname ). to . equal ( bookingData [ i - 1 ]. firstname )
expect ( lastname ). to . equal ( bookingData [ i - 1 ]. lastname )
expect ( totalprice ). to . equal ( bookingData [ i - 1 ]. totalprice )
expect ( depositpaid ). to . equal ( bookingData [ i - 1 ]. depositpaid )
})
}
})
})
This approach works well for controlled test environments. For public APIs, you may need to use more flexible assertions.
Advanced API Testing Patterns
Pattern 1: Request Chaining
it ( 'Should create and retrieve booking' , () => {
let bookingId : number
// Create booking
cy . request ({
method: 'POST' ,
url: '/booking' ,
body: {
firstname: 'Test' ,
lastname: 'User' ,
totalprice: 100 ,
depositpaid: true ,
bookingdates: {
checkin: '2024-01-01' ,
checkout: '2024-01-10'
}
}
}). then (( res ) => {
expect ( res . status ). to . eq ( 200 )
bookingId = res . body . bookingid
// Retrieve created booking
cy . request ( `/booking/ ${ bookingId } ` ). then (( getRes ) => {
expect ( getRes . body . firstname ). to . eq ( 'Test' )
expect ( getRes . body . lastname ). to . eq ( 'User' )
})
})
})
Pattern 2: Authentication Flow
it ( 'Should authenticate and access protected endpoint' , () => {
let authToken : string
// Get auth token
cy . request ({
method: 'POST' ,
url: '/auth' ,
body: {
username: 'admin' ,
password: 'password123'
}
}). then (( res ) => {
authToken = res . body . token
// Use token for authenticated request
cy . request ({
method: 'DELETE' ,
url: '/booking/1' ,
headers: {
'Cookie' : `token= ${ authToken } `
}
}). its ( 'status' ). should ( 'eq' , 201 )
})
})
Pattern 3: Data-Driven Testing
it ( 'Should handle multiple booking queries' , () => {
const queries = [
{ firstname: 'John' , lastname: 'Doe' },
{ firstname: 'Jane' , lastname: 'Smith' },
{ firstname: 'Bob' , lastname: 'Johnson' }
]
queries . forEach ( query => {
cy . request ({
method: 'GET' ,
url: '/booking' ,
qs: query
}). then (( res ) => {
expect ( res . status ). to . eq ( 200 )
expect ( res . body ). to . be . an ( 'array' )
})
})
})
Pattern 4: Error Handling
it ( 'Should handle invalid request gracefully' , () => {
cy . request ({
method: 'POST' ,
url: '/booking' ,
body: {
// Missing required fields
firstname: 'Test'
},
failOnStatusCode: false
}). then (( res ) => {
expect ( res . status ). to . be . oneOf ([ 400 , 500 ])
expect ( res . body ). to . have . property ( 'error' )
})
})
Schema Validation
Validate response structure using Cypress assertions:
it ( 'Should match booking schema' , () => {
cy . request ( '/booking/1' ). then (( res ) => {
const booking = res . body
// Validate top-level properties
expect ( booking ). to . have . all . keys (
'firstname' ,
'lastname' ,
'totalprice' ,
'depositpaid' ,
'bookingdates' ,
'additionalneeds'
)
// Validate nested object
expect ( booking . bookingdates ). to . have . all . keys ( 'checkin' , 'checkout' )
// Validate types
expect ( booking . firstname ). to . be . a ( 'string' )
expect ( booking . totalprice ). to . be . a ( 'number' )
expect ( booking . depositpaid ). to . be . a ( 'boolean' )
})
})
Testing Different HTTP Methods
it ( 'Should retrieve resource' , () => {
cy . request ( 'GET' , '/booking/1' )
. its ( 'status' )
. should ( 'eq' , 200 )
})
Best Practices
Keep tests focused on a single assertion: // ✅ Good - focused test
it ( 'Should return 200 status' , () => {
cy . request ( '/booking' ). its ( 'status' ). should ( 'eq' , 200 )
})
it ( 'Should return array' , () => {
cy . request ( '/booking' ). then ( res => {
expect ( res . body ). to . be . an ( 'array' )
})
})
// ❌ Bad - testing multiple things
it ( 'Should work correctly' , () => {
cy . request ( '/booking' ). then ( res => {
expect ( res . status ). to . eq ( 200 )
expect ( res . body ). to . be . an ( 'array' )
expect ( res . headers [ 'content-type' ]). to . include ( 'json' )
})
})
Use failOnStatusCode wisely
Set failOnStatusCode: false when testing error cases: it ( 'Should return 404 for invalid ID' , () => {
cy . request ({
method: 'GET' ,
url: '/booking/invalid' ,
failOnStatusCode: false
}). its ( 'status' ). should ( 'eq' , 404 )
})
Include performance assertions: it ( 'Should respond quickly' , () => {
cy . request ( '/booking' ). then ( res => {
expect ( res . duration ). to . be . lessThan ( 2000 )
})
})
Delete created resources after tests: it ( 'Should create and clean up booking' , () => {
cy . request ( 'POST' , '/booking' , { /* data */ })
. then ( res => {
const id = res . body . bookingid
// Test logic here
// Clean up
cy . request ({
method: 'DELETE' ,
url: `/booking/ ${ id } ` ,
headers: { 'Cookie' : 'token=abc' }
})
})
})
Running API Tests
Interactive Mode
Headless Mode
Single Test File
API tests run much faster than UI tests since they don’t involve browser rendering.
Next Steps
Using Fixtures Manage API test data with fixtures
Running Tests Learn all test execution options
Test Organization Structure your API test suite
Maintainability Keep API tests maintainable