Clean API tests with cypress aliases
When writing API tests in Cypress, you often face the same problem: you want to reuse the same request multiple times or inspect the response in different places.
It’s tempting to use separate .then() chains everywhere or store the response in variables. But as your test grows, this quickly leads to messy, nested code. It looks something like this:
cy.request({
method: 'POST',
url: '/api/pets',
body: { name: 'Bobby', type: 'dog' }
}).then((createResponse) => {
expect(createResponse.status).to.eq(201);
const petId = createResponse.body.id;
cy.request({
method: 'GET',
url: `/api/pets/${petId}`
}).then((getResponse) => {
expect(getResponse.status).to.eq(200);
expect(getResponse.body.name).to.eq('Bobby');
cy.request({
method: 'GET',
url: `/api/pets/${petId}`
}).then((secondGetResponse) => {
expect(secondGetResponse.body.type).to.eq('dog');
});
});
});
Fortunately, Cypress has a solution: aliases with .as(). This lets you save a request under a name and easily reference it later with cy.get('@alias'). This makes your tests clearer, more reusable, and this means also easier to maintain.
The API Functions
Let’s start with two simple helper functions:
createPet: to create a new petgetPet: to retrieve a pet by ID
// Create a new pet
export function createPet(
pet: { name: string; type: 'cat' | 'dog' },
wrappedAs = 'createPet'
) {
return cy
.request({
method: 'POST',
url: '/api/pets',
body: pet,
})
.as(wrappedAs); // store the request as an alias
}
// Get a pet by ID
export function getPet(
petId: string,
wrappedAs = 'getPet'
) {
return cy
.request({
method: 'GET',
url: `/api/pets/${petId}`,
})
.as(wrappedAs); // store the request as an alias
}
Free tip about the wrappedAs parameter
The wrappedAs parameter lets you assign your own alias name. By default, it’s 'createPet' or 'getPet', but if you create multiple pets at once, you can use 'createPet1' and 'createPet2' to avoid alias conflicts.
If two requests use the same alias, the second one automatically overwrites the first. For example:
createPet({ name: 'Luna', type: 'cat' }).as('createPet');
createPet({ name: 'Max', type: 'dog' }).as('createPet');
Here, cy.get('@createPet') will always refer to the second request (Max). The first request (Luna) is effectively lost and that reply can not be checked again afterwards.
By using unique aliases, like this:
createPet({ name: 'Luna', type: 'cat' }, 'createPet1');
createPet({ name: 'Max', type: 'dog' }, 'createPet2');
you can access and inspect both responses independently without overwriting. This keeps your tests safer and more readable when performing multiple similar requests.
Using the Aliases
Basic usage
With aliases, you can easily inspect or reuse the response later in your test.
// Create a new pet
createPet({ name: 'Bobby', type: 'dog' });
// Use the alias to check the response
cy.get('@createPet')
.its('status')
.should('eq', 201);
In this example, Cypress automatically saves the POST request under the alias @createPet.
Thanks to the alias, you can refer to this request at any point later in your test without sending it again. This makes your tests shorter, cleaner, and easier to maintain, especially if you want to inspect or reuse the same response multiple times.
Inspect directly is possible too
If you want to perform assertions immediately or extract data, use .then():
// Create a new pet and inspect the response immediately
createPet({ name: 'Bobby', type: 'dog' }).then((response) => {
expect(response.status).to.eq(201);
expect(response.body).to.have.property('name', 'Bobby');
expect(response.body).to.have.property('type', 'dog');
});
This approach lets you access the response as soon as it’s available, so you can run assertions or extract specific data like IDs or property values without waiting for a separate step. It’s especially useful when you need to immediately validate the response or pass data to subsequent requests in the same test.
Chaining requests
A common scenario in API tests is creating a pet first and then retrieving the same pet to verify the data. With aliases, this is simple and clean:
// Create a new pet
createPet({ name: 'Luna', type: 'cat' });
// Get the petId from the create alias and immediately do a GET
cy.get('@createPet').then((createResponse) => {
const petId = createResponse.body.id;
// Execute GET request using that ID
getPet(petId);
});
// Inspect the GET response via the getPet alias
cy.get('@getPet').then((getResponse) => {
expect(getResponse.status).to.eq(200);
expect(getResponse.body).to.have.property('name', 'Luna');
expect(getResponse.body).to.have.property('type', 'cat');
});
This example explained
- Execute POST and save as alias:
The first step creates a new pet with
createPet()and stores the response as@createPet. This allows you to retrieve it later without sending the request again. You can give your own alias, but here I made it simpler by using the defaults. - Extract ID for follow-up request:
Using
.then(), you can get theidof the created pet from the POST response and use it in the next step. - Execute GET request with the ID:
The GET request is executed via
getPet(petId)to retrieve the pet’s data. This response is also stored as an alias,@getPet. - Inspect response:
Using
cy.get('@getPet'), you can assert that the GET response matches what you expect, e.g., name and type. - Maintain readability: Thanks to this pattern, tests remain clear and readable, even with multiple dependent requests. You don’t need complex nesting or separate variables, keeping your code maintainable.
Why do I use .as()?
Cypress executes all commands asynchronously. This means if you make a request and try to use the response immediately without an alias, you risk getting undefined because the data isn’t ready yet. Using .as() stores the request as an alias, so Cypress knows exactly when the request is complete and you can safely access the response:
createPet({ name: 'Bobby', type: 'dog' });
// Cypress automatically waits until the POST is finished
cy.get('@createPet').its('status').should('eq', 201);
A major benefit of aliases is that you can reuse the same response multiple times without creating extra variables or resending requests:
createPet({ name: 'Luna', type: 'cat' });
cy.get('@createPet').then((response) => {
const petId = response.body.id;
getPet(petId);
// Retrieve the same create response again
cy.get('@createPet').its('body.name').should('eq', 'Luna');
});
Without aliases, you would need to manually store each response in a separate .then() or variable for every step, which quickly makes your test code messy, complex, and hard to maintain. Aliases make this process much cleaner and easier to read.
Aliases are also ideal for chaining dependent requests, for example, a GET request that depends on a previously executed POST request:
createPet({ name: 'Luna', type: 'cat' });
cy.get('@createPet').then((createResponse) => {
const petId = createResponse.body.id;
getPet(petId);
});
cy.get('@getPet').its('status').should('eq', 200);
Here, Cypress automatically waits until the POST request is fully completed and the GET response is available before checking the status. This prevents race conditions and errors in your tests, keeping your code both safe and readable.
Resources
- Photo of a cat in iron that I found on one of my hikes.
- Code is based on the Pets API, but with simplified data.