Understand the differences between cy.request
and cy.intercept
and use them to write robust automated Cypress tests.
Some people make confusions between the cy.request
and cy.intercept
commands. However, they're quite different.
In this content, I will explain both.
cy.request
As its name suggests, cy.request
is the command used to perform requests at the network level, such as a GET
or POST
.
Such a command is useful when the application under test has a public API (application programming interface), which allows, for example, the creation of resources in an optimized way (i.e., without the need to go through the graphical user interface or GUI).
Let's imagine the following test case.
We have a sample clone of the Trello application, which allows you to create boards, board columns, and cards.
We want to test the card creation.
However, it is a prerequisite for the test that there is a board with at least one column.
Creating the board and column via GUI would be a waste, as there are probably GUI tests focused on these features already. Also, the test would be slow (it would spend more time creating the pre-conditions than testing the functionality of creating a card) and GUI-laden, which is not a good practice if you are looking for automated tests that scale along with the application.
Let's see how we could optimize this process using cy.request
.
describe('Trello App', () => {
beforeEach(() => {
cy.request({
method: 'POST',
url: '/boards',
body: { title: 'TAT' }
})
cy.request({
method: 'POST',
url: '/boards/tat',
body: { column: 'TODO' }
})
})
it("creates a card in a board's column", => {
cy.visit('/boards/tat')
// The logic of card creation via GUI...
})
})
In the above test, only the card creation takes place via GUI. In contrast, its pre-conditions (board and column creation) occur via API calls using the cy.request
command in the beforeEach
hook, making the test faster and more robust.
Another example of using cy.request
is when we want to test an API itself.
Let's look at an example:
describe('Trello API', () => {
it('POST /boards', => {
cy.request({
method: 'POST',
url: '/boards',
body: { title: 'TAT' }
}).should(response => {
expect(response.status).to.equal(201)
expect(response.body.title).to.equal('TAT')
})
})
})
In the above test, the focus is only on the request, without any interaction via the GUI, where we only guarantee the expected status
and content of the body
.
For more details about the cy.request
command, visit the Cypress.io official docs.
cy.intercept
cy.intercept
does not make a request but rather "listens" to requests on the network layer. If we "ask" Cypress to name a particular request that we expect to happen after some action, we can also "ask" it to wait for it before moving on when it notices that such a request occurred.
That is, cy.intercept
is usually used in conjunction with two other commands, .as
and cy.wait
.
Here's an example of a GUI test that creates a card for a board (in the same Trello application), and ensures that, after creating the card (via GUI) a request to fetch the cards is triggered, which is in charge of loading the new card on the board into the frontend. That way, we can proceed with the verification only after such a request is finished.
it("creates a card in a board's column", => {
cy.intercept('GET', '**/boards/tat/cards').as('getCards')
cy.visit('/boards/tat')
cy.get('[data-cy="add-card-btn"]').click()
cy.get('[data-cy="card-title-input"]').type('Bug #420')
cy.get('[data-cy="card-title-description"]').type('Test bug #420 in production')
cy.get('[data-cy="create-card-btn"]').click()
cy.wait('@getCards')
cy.contains('[data-cy="card"]', 'Bug #420').should('be.visible')
})
In the above test, we first define cy.intercept
and give it an alias with the command .as('getCards')
Then comes the logic that creates the card via GUI, where we visit the page of a previously created board, click the button to add a new card, type the card's title and description, and finally, click the button that creates the card itself.
At this point, the getCards
request should be triggered, so we use cy.wait('@getCards')
to only proceed to the next step (which asserts that the created card is visible) after the request ends.
Other things we can do with cy.intercept
are:
- Reply to a request with a static
JSON
file defined in thefixtures/
folder (i.e., we can mock the response) - Handle the request ourselves
- Change the request's
statusCode
to simulate a server failure - Simulate a network failure
- Simulate a delay in the request
For more details about the cy.intercept
command, visit the Cypress.io official docs.
If you had paid attention to all the code examples, you might have noticed that they form a final version of themselves if we had put the first and the last code examples together.
That is, the test's pre-conditions are created via API calls (using cy.request
), and the test itself creates a Trello card via the GUI (waits for the proper request to happen using a combination of cy.intercept(...).as(...)
and cy.wait(...)
).
Let's see the final version.
describe('Trello App', () => {
beforeEach(() => {
cy.request({
method: 'POST',
url: '/boards',
body: { title: 'TAT' }
})
cy.request({
method: 'POST',
url: '/boards/tat',
body: { column: 'TODO' }
})
})
it("creates a card in a board's column", => {
cy.intercept('GET', '**/boards/tat/cards').as('getCards')
cy.visit('/boards/tat')
cy.get('[data-cy="add-card-btn"]').click()
cy.get('[data-cy="card-title-input"]').type('Bug #420')
cy.get('[data-cy="card-title-description"]').type('Test bug #420 in production')
cy.get('[data-cy="create-card-btn"]').click()
cy.wait('@getCards')
cy.contains('[data-cy="card"]', 'Bug #420').should('be.visible')
})
})
I hope the difference is clear. If not, leave a comment, and let's talk.
Happy testing! 🕵️
Top comments (2)
We can wait for element to appear on UI right. Then why to user intercept?
You can use it as an extra layer to ensure the app is at the expected state before moving on to the next steps or assertions.