Here is what I am trying to do in the code below.
Make three API calls inside the function areAllTypesFound
.
Use the results of those API calls to decide whether I got all the necessary data.
If I get all the necessary data from the calls, then return a boolean wrapped inside a chainable.
In the function doStuff
, I make a call to areAllTypesFound
. only after that call is completed, I will do something with the boolean result. After that, I will execute some more code.
The problem is that areAllTypesFound
always returns false even when all the data is found. I am quite sure this has to do something with the async nature of Cypress. Can someone please show me how to execute everything synchronously in the below code?
class MyClass {
static areAllTypesFound(params) : Cypress.Chainable<boolean> {
let idsType1 : number[];
let idsType2 :number[];
let idsType3 :number[];
let [type1Present, type2Present, type3Present] = [false, false, false];
// Here response = Cypress.Response<ItemType1 2 or 3>
ApiCaller.getItemType1(param1).then((response) => {
idsType1 = response.body.items.map((item) => item.id);
type1Present = idsType1.length > 0;
}
ApiCaller.getItemType2(param2).then((response) => {
idsType2 = response.body.items.map((item) => item.id);
type2Present = idsType2.length > 0;
}
ApiCaller.getItemType3(param3).then((response) => {
idsType3 = response.body.items.map((item) => item.id);
type3Present = idsType3.length > 0;
}
const allTypesPresent = type1Present && type2Present && type3Present;
// optional - maybe print all the "idsType" arrays.
return cy.wrap(allTypesPresent);
}
static doStuff() : void {
this.areAllTypesFound(params).then((areFoundBoolean) => {
if(areFoundBoolean) {
// do something
}else {
// do something else
}
});
// do more stuff only AFTER the above code is completed !
}
}
Sample code for what itemType function looks like -
public static getItemType1<T = ItemType1>(param1: string): Cypress.Chainable<Cypress.Response<T>> {
return cy.request({
method: "GET",
url: "/api/items/itemType1",
qs: {
itemType: param1,
},
headers: {
accept: "application/json",
},
});
}
You could nest the API calls. There may be a more elegant way, but nesting the calls and returning at each level ensures you have completed each call before the final return is made.
const allResults = [] // gather the results in an array
return ApiCaller.getItemType1(param1).then((response) => {
idsType1 = response.body.items.map((item) => item.id);
allResults.push(idsType1)
return ApiCaller.getItemType2(param2).then((response) => {
idsType2 = response.body.items.map((item) => item.id);
allResults.push(idsType2)
return ApiCaller.getItemType3(param3).then((response) => {
idsType2 = response.body.items.map((item) => item.id);
allResults.push(idsType2)
return allResults
}
}
}
You can put anything in the allResults
, as long as you get 3 things in the array.
I wanted to explore the code in question in a reproducible form.
There's a useful website requestly.com
Build, Test & Debug your web apps faster by Intercepting & Modifying Network Traffic, Session Replays, Mock Server & API Client.
This site can add delays (or other modifications) to your cy.request()
calls, which allows you to stress-test your test code to make sure it's not flaky.
The way you use it is to embed the delay time in milliseconds and the actual API URL like this:
https://app.requestly.io/delay/<delay_in_millisecods>/<URL_to_be_delayed
The API I want to test is https://jsonplaceholder.typicode.com/todos/
, requesting records with id's 1, 2, and 3.
The goal is to ensure all three results are returned from the function (or in the OP it's a class method, same idea).
function apiCalls() {
const myApiBase = 'https://jsonplaceholder.typicode.com/todos/'
const urlWithDelay = 'https://app.requestly.io/delay/1000/' + myApiBase
return cy.request(urlWithDelay + '1') // record id 1
.then(response1 => {
return cy.request(urlWithDelay + '2') // record id 2
.then(response2 => {
return cy.request(urlWithDelay + '3') // record id 3
.then(response3 => {
return [response1.body.title, response2.body.title, response3.body.title]
})
})
})
}
apiCalls().then(console.log)
.should(results => {
expect(results[0]).to.eq('delectus aut autem')
expect(results[1]).to.eq('quis ut nam facilis et officia qui')
expect(results[2]).to.eq('fugiat veniam minus')
})
It turns out, after testing, that you don't need nested cy.request()
calls, you can force the function to wait for all requests by using cy.then()
(which was my original off-the-cuff answer BTW).
function apiCalls() {
let api1
let api2
let api3
const myApiBase = 'https://jsonplaceholder.typicode.com/todos/'
const urlWithDelay = 'https://app.requestly.io/delay/1000/' + myApiBase
cy.request(urlWithDelay + '1')
.then(response => { api1 = response.body.title })
cy.request(urlWithDelay + '2')
.then(response => { api2 = response.body.title })
cy.request(urlWithDelay + '3')
.then(response => { api3 = response.body.title })
return cy.then(() => [api1, api2, api3])
}
apiCalls()
.should(results => {
expect(results[0]).to.eq('delectus aut autem')
expect(results[1]).to.eq('quis ut nam facilis et officia qui')
expect(results[2]).to.eq('fugiat veniam minus')
})
So cy.then()
behaves like async/await
in plain Javascript.