Cypress MOCK api response for different status - cypress

I am testing my login component with Cypress (just started with it) and I want to handle three different cases where the API returns status 200, 400 or 500. I want to mock these responses to see how the frontend responds to that.
I want to mock the response for three different cases (200, 400 and 500) when sending a request to my API endpoint http://localhost:9999/api/login
I have written some code based on the docs but I still am not where I want to be.
describe('Login Approach', () => {
it('login', () => {
cy.visit('/login')
// these values email and pw shouldn't matter if mocking is done right
cy.get('#email')
.type('test')
.should('have.value', 'test')
cy.get('#password')
.type('123456')
.should('have.value', '123456')
cy.server()
cy.route({
method: 'POST',
url: 'http://localhost:9999/api/login', // this is the api that I send the request to
})
cy.location('pathname', { timeout: 10000 }).should('eq', '/login');
cy.title().should('include', 'Condeo')
cy.get('#notification').should('exist')
})
})
I am not getting status in the details of the test:
Method Url Stubbed Alias #
POST http://localhost:9999/api/login Yes -

You should use the wait method of cypress.
You can find the cypress documentation here.
For your use case, make sure you start the server and define the route before you visit the link. Just after visiting the link, use the cy.wait() method which will wait for that API call to finish.
Eg.
describe('Login Approach', () => {
it('login', () => {
cy.visit('/login')
// these values email and pw shouldn't matter if mocking is done right
cy.get('#email')
.type('test')
.should('have.value', 'test')
cy.get('#password')
.type('123456')
.should('have.value', '123456')
cy.server()
cy.route({
method: 'POST',
url: 'http://localhost:9999/api/login', // this is the api that I send the request to
}).as('login')
cy.location('pathname', { timeout: 10000 }).should('eq', '/login');
cy.title().should('include', 'Condeo')
cy.get('#notification').should('exist')
// Code which will try to visit the login API.
cy.wait('#login').then((xhr)=> {
if(xhr.status === 200) {
// Code to test when status is 200
} else if(xhr.status === 400) {
// Code to test when status is 400
} else {
// Code to test when status is none of the above.
}
})
})
})

Related

Cypress intercept does not match route with status 204

I am implementing cypress tests in our Angular application and have a problem waiting for a request to finish. I am guessing it has to do with the status of the Request being 204 instead of 200.
This is the function/command I am calling in my test:
export function logout() {
cy.intercept('/api/security/logout').as('logoutRequest');
cy.getCookie('SESSION').then((cookie) => {
if (cookie != null) {
cy.request(
{
method: 'POST',
url: '/api/security/logout',
}
);
}
});
cy.wait('#logoutRequest');
}
My problem is, that the route /api/security/logout is not recognized as alias #logoutRequest and therefore the wait always timeouts. Even though there is a vaild request. As you can see here in the test protocol:
I have tried modifing the route with * or ** but without success. I would be very glad if you could help me out.
You can't use cy.intercept() to catch cy.request().
cy.intercept(), cy.server(), and cy.route()
cy.request() sends requests to actual endpoints, bypassing those defined using cy.route() or cy.intercept()
Just chain .then() off the request to handle the reply
cy.request({method: 'POST', url: '/api/security/logout', failOnStatusCode: false})
.then(response => {
expect(response.status).to.eq(200)
})

How to return HTTP response body from Cypress custom command?

I am trying to write a custom Cypress command that sends a POST request to an endpoint, & I then want to store the response body in my test.
Here is what the response body looks like in Postman:
Here is my custom command in cypress/support/commands.js, for simplicity, I've removed the request body values:
Cypress.Commands.add('createStudent', (email) => {
cy.request({
method: `POST`,
url: `myUrl`,
body: {}
}).then((resp) => {
return resp
});
});
Here is the code in my spec file:
let response = cy.createStudent(email);
cy.log(response)
However, when I run the code I get back the below object rather than the response body:
Can someone please tell me where I am going wrong, & what changes are required to return the actual HTTP response body?
If you look at the console message, there's a type $Chainer shown which is a wrapper object around the result you actually want (response).
The Chainer is fundamental to Cypress being able to retry queries that fail initially but may succeed within a timeout period (usually 4 seconds).
But it means you can't use the return value. Instead you need to "unwrap" the value using .then().
Cypress.Commands.add('createStudent', (email) => {
cy.request({
method: 'POST',
url: 'myUrl',
body: {...}
})
// The response is put on the "chain" upon exit of the custom command
// You need nothing else here to get the raw response
})
cy.createStudent().then(response => {
cy.log(response)
});
You can add a step to extract details from the response, like
Cypress.Commands.add('createStudent', (email) => {
cy.request({
method: 'POST',
url: 'myUrl',
body: {...}
})
.then(response => {
expect(response.success).to.eq(true) // check expected response is good
return response.body.id // pass on just the id
})
})
cy.createStudent().then(id => {
cy.log(id)
});
If you'll only ever be using the value in a Cypress chain, you could simply alias the command.
Cypress.Commands.add('createStudent', (email) => {
cy.request({
method: `POST`,
url: `myUrl`,
body: {}
}).as('student');
});
...
cy.createStudent();
cy.get('#student').then((response) => {
cy.log(response.body) // assuming you'd want to log the response body.
});
// OR
cy.get('#student').its('body').should('eq', { foo: 'bar' });
// the above example doesn't work with logging, but I'm guessing you don't _just_ want to log the response
If you may need the variable at other times outside of a Cypress chain, you could always stash the variable in Cypress.env().
Cypress.Commands.add('createStudent', (email) => {
cy.request({
method: `POST`,
url: `myUrl`,
body: {}
}).then((res) => {
Cypress.env('student', res);
});
});
...
cy.createStudent().then(() => {
cy.get('foo').should('have.text', Cypress.env('student').body.foo);
});
// key point is referencing the entire response by `Cypress.env('student')`

How to mock a successful response in cypress, only when the endpoint return is 503?

I would like to wait for an endpoint to respond, if the answer is 200 I would like to continue the test using the body returned from the endpoint, but if the return is a 503 for example, I would like to mock the answer with a 200 and use the body mock, is it possible?
In Cypress version 6 or greater you can use cy.intercept()
You could listen for an endpoint and stub the status code.
Here is an example using the pokemon api. This will stub the response to always send a status of 200 but keep the original response body.
it("should stub the status code", () => {
cy.intercept("GET", "pokemon/4", (req) => {
req.reply((res) => {
res.send(200, res.body);
});
}).as("charmander");
cy.visit("https://pokeapi.co/");
cy.get("#url-input").clear().type("pokemon/4");
cy.contains("Submit").click();
cy.wait("#charmander").then((res) => {
//the stubbed response
console.log(res);
});
});
See the cypress docs for further details https://docs.cypress.io/api/commands/intercept.html

How to wait for a successful response in Cypress tests

Background
I use 3 back-end servers to provide fault tolerance for one of my online SaaS application. All important API calls, such as getting user data, contact all 3 servers and use value of first successfully resolved response, if any.
export function getSuccessValueOrThrow$<T>(
observables$: Observable<T>[],
tryUntilMillies = 30000,
): Observable<T> {
return race(
...observables$.map(observable$ => {
return observable$.pipe(
timeout(tryUntilMillies),
catchError(err => {
return of(err).pipe(delay(5000), mergeMap(_err => throwError(_err)));
}),
);
})
);
}
getSuccessValueOrThrow$ get called as following:
const shuffledApiDomainList = ['server1-domain', 'server2-domain', 'server3-domain';
const sessionInfo = await RequestUtils.getSuccessValueOrThrow(
...(shuffledApiDomainList.map(shuffledDomain => this.http.get<SessionDetails>(`${shuffledDomain}/file/converter/comm/session/info`))),
).toPromise();
Note: if one request resolve faster than others, usually the case, race rxjs function will cancel the other two requests. On Chrome dev network tab it will look like bellow where first request sent out was cancelled due to being too slow.
Question:
I use /file/converter/comm/session/info (lets call it Endpoint 1) to get some data related to a user. This request dispatched to all 3 back-end servers. If one resolve, then remaining 2 request will be cancelled, i.e. they will return null.
On my Cypress E2E test I have
cy.route('GET', '/file/converter/comm/session/info').as('getSessionInfo');
cy.visit('https://www.ps2pdf.com/compress-mp4');
cy.wait('#getSessionInfo').its('status').should('eq', 200)
This sometimes fails if the since getSessionInfo alias was hooked on to a request that ultimately get cancelled by getSuccessValueOrThrow$ because it wasn't the request that succeeded.Bellow image shows how 1 out of 3 request aliased with getSessionInfo succeeded but the test failed since the first request failed.
In Cypress, how do I wait for a successful i.e. status = 200 request?
Approach 1
Use .should() callback and repeat the cy.wait call if status was not 200:
function waitFor200(routeAlias, retries = 2) {
cy.wait(routeAlias).then(xhr => {
if (xhr.status === 200) return // OK
else if (retries > 0) waitFor200(routeAlias, retries - 1); // wait for the next response
else throw "All requests returned non-200 response";
})
}
// Usage example.
// Note that no assertions are chained here,
// the check has been performed inside this function already.
waitFor200('#getSessionInfo');
// Proceed with your test
cy.get('button').click(); // ...
Approach 2
Revise what it is that you want to test in the first place.
Chances are - there is something on the page that tells the user about a successful operation. E.g. show/hide a spinner or a progress bar, or just that the page content is updated to show new data fetched from the backend.
So in this approach you would remove cy.wait() altogether, and focus on what the user sees on the page - do some assertions on the actual page content.
cy.wait() yields an object containing the HTTP request and response properties of the XHR. The error you're getting is because you're looking for property status in the XHR object, but it is a property of the Response Object. You first have to get to the Response Object:
cy.wait('#getSessionInfo').should(xhr => {
expect(xhr.response).to.have.property('status', 200);
});
Edit: Since our backend uses graphql, all calls use the single /graphql endpoint. So I had to come up with a solution to differentiate each call.
I did that by using the onResponse() method of cy.route() and accumulating the data in Cypress environment object:
cy.route({
method: 'GET',
url: '/file/converter/comm/session/info',
onResponse(xhr) {
if (xhr.status === 200) {
Cypress.env('sessionInfo200') = xhr;
}
}
})
You can then use it like this:
cy.wrap(Cypress.env()).should('have.property', 'sessionInfo200');
I wait like this:
const isOk = cy.wait("#getSessionInfo").then((xhr) => {
return (xhr.status === 200);
});

Angular Js - Test POST request that returns an object with Jasmine

I have a service set up which makes all my AJAX calls. I want to test my login method, which sends an AJAX POST $http.post to a particular URL, which returns an objects with the result (login passed or failed). This result is an object. I have not return the code exactly to test the service, but I'm trying to test the URL first. This is how it looks right now:
'use strict';
describe('Service: Ajax', function () {
var service, httpBackend;
// load the service's module
beforeEach(module('mySampleApp'));
// instantiate service
beforeEach(inject(function (Ajax, _$httpBackend_) {
httpBackend = _$httpBackend_;
service = Ajax;
}));
it('Test AJAX call', function () {
httpBackend.expect('POST', 'http://myloginurl', {u: 'xyz', p: 'pass'}, { withCredentials: true})
.respond(201, 'success');
});
});
This passes. Now I tried putting a wrong URL, wrong username/password, but it still passes! How do I test this?
UPDATE:
Better written now:
//Ajax is my AJAX service
it('should test the login AJAX call', inject(function (Ajax) {
httpBackend.expect('POST', 'http://myloginurl')
.respond(200, "[{ status: 200, //some more data }]");
httpBackend.flush();
Ajax.authenticate({u: 'xyz', password: 'pass' })
.then(function(data){
expect(data.status).toBe(200);
});
}));
I get this:
PhantomJS 1.9.7 (Linux) Service: Ajax should test the login AJAX call FAILED
Error: No pending request to flush !
blah blah...
You need to put a
httpBackend.flush();
in that will throw an exception if the expected url wasn't called - thereby failing your test.
Also, I can't see that you're calling the code that does the Ajax request anywhere - you need to do that before calling flush().
So something like:
it('Test AJAX call', function () {
httpBackend.expect('POST', 'http://myloginurl', {u: 'xyz', p: 'pass'}, { withCredentials: true})
.respond(201, 'success');
service.functionThatMakesCallToServer();
httpBackend.flush();
});
If functionThatMakesCallToServer() calls the url in the httpBackend.expect(...) line, everything will be ok. If it doesn't httpBackend.flush() will throw an error as a call that was expected did not happen. The error will cause your test to fail.

Resources