cypress-api-tutorial
Repository created in the article REST API Tests with Cypress
Posted on May 23, 2021
Moving forward with the subject we discussed in the previous post, let's focus on to the part that is most important to us, the tests.
The resources of a microservice based applications can be manipulated in several ways. It is possible to create, update, delete them, and do other operations as well.
When we send a request for a service, we need to inform the URI to identify which resources we intend to handle. We also need to inform the type of manipulation we want to do on the resource. For this, we will use the HTTP protocol methods.
The HTTP protocol has several methods, each one has a different function. The most used are:
Back to our target API - ServeRest, let's start by testing the user's endpoint:
/usuarios
to retrieve all users data.So, move your asses. Let's do it!
We will remove all the contents of the previous post from the Integration folder. That’s where our tests should be, so we’ll just leave what’s needed.
You can remove it manually or use the command below if you are lazy like me:
rm -rf cypress/integration/*
Now we are going to create a folder called Usuarios
, which we will use to store all requests related to the User's endpoint. This will keep our code organized.
Inside the Usuarios
folder, we will create our first test spec, called GETusuarios.spec.js
Now the project structure should look like this:
├── fixtures
├── integration
│ └── Usuarios
│ └── GETUsuarios.spec.js
├── plugins
│ └── index.js
├── support
│ ├── commands.js
│ └── index.js
└── videos
Let's add the basic Mocha organization structure. You can use BDD style if you want:
Given
-> Test Suite name
When
-> Test Inner Suite name
Then
-> You should place your tests here!
/// <reference types="cypress" />
describe('Given the Users api', () => {
context('When I send GET /usuarios', () => {
it('Then it should return a list with all registered users', () => {
// place your tests here
});
});
context('When I send GET /usuarios passing id query param', () => {
it('Then it should return only the filtered user', () => {
// place your tests here
});
});
});
Now let's use Cypress method cy.request
to make the HTTP request:
cy.request({
method: 'GET',
url: 'https://serverest.dev/usuarios'
})
After that we will call the `.should` function, which I mentioned in the previous post. It will enable us to make multiple assertions on the yielded subject - **response** in this case.
```javascript
.should((response) => {
// all your assertions should be placed here!!
});
Let's add a log to see what the 'response' is returning in the body:
cy.log(JSON.stringify(response.body))
In short, cy.log
will access the property 'body' of the 'response'. The JSON.stringify
function will be used to transform the response body into a string.
Run the command cypress:open
and check what the log returns to us.
npm run cypress:open
Awesome! We can conclude that our call is working correctly, as we are receiving the response body correctly and 200 status code (success).
Let's remove cy.log
(we don't want trash in our tests) and add some status code and response body assertions.
We can validate the easiest one first, the status code:
expect(response.status).to.eq(200)
Cool! The above code means that we expect the status code of the response to be equal to 200.
We can assert that the quantidade (quantity) key will always have the same number of the usuarios (users) array. Lets add this validation:
expect(response.body.quantidade).to.eq(response.body.usuarios.length)
Let's validate that the email
from usuarios
field should not be null...
Hmm, but
usuarios
is a list with three elements. So how do we do that?
We need to pass which index from usuarios
list we want to access to do this. For now, let's access the first object in the list, passing 'usuarios[0]':
expect(response.body.usuarios[0].email).to.not.be.null
Great, it worked! But what can we ensure that the 'email' key of all objects within the usuarios
array is not null?
Maybe duplicate the previous row, just changing the index?
expect(response.body.usuarios[0].email).to.not.be.null
expect(response.body.usuarios[1].email).to.not.be.null
Yeah, it might even work. BUT what if we had a thousand of users inside this array, would we add a thousand lines to our code?
I don't think so.
To do a smarter assertion, we can use Cypress loadash
, which offers us the .each()
function:
Cypress._.each(response.body.usuarios, (usuario) => {
expect(usuario.email).to.not.be.null
})
This function work almost like a forEach(), going through the array and making the assertions now in each 'usuario' object from 'usuarios' array.
Let's take the opportunity to add one last validation to usuarios
. We expect each object to have all the keys ('nome', 'email', 'password', 'administrador', '_id'):
expect(usuario).to.have.all.keys('nome', 'email', 'password', 'administrador', '_id')
Moving to the next test case, we will send the same request as before, but this time passing a query string
to filter only one user by _id
:
Adding a validation to ensure that the name is always correct:
context('When I send GET /usuarios passing id query param', () => {
it('Then it should return only the filtered user', () => {
cy.request({
method: 'GET',
url: 'https://serverest.dev/usuarios',
qs: {
_id: '0uxuPY0cbmQhpEz1'
}
})
.should((response) => {
expect(response.status).to.eq(200)
expect(response.body.usuarios[0].nome).to.eq("Fulano da Silva")
});
});
});
We are repeating the url
parameter in both cy.request()
. Add the following lines to your cypress.json
file, so that it is not necessary to repeat this information.
Set video
as false
as well. We don't want to Cypress record it for us.
{
"baseUrl": "https://serverest.dev",
"video": false
}
Okay, that was a good start and now our code looks like this:
/// <reference types="cypress" />
describe('Given the Users api', () => {
context('When I send GET /usuarios', () => {
it('Then it should return a list with all registered users', () => {
cy.request({
method: 'GET',
url: '/usuarios'
})
.should((response) => {
expect(response.status).to.eq(200)
expect(response.body.quantidade).to.eq(response.body.usuarios.length)
Cypress._.each(response.body.usuarios, (usuario) => {
expect(usuario.email).to.not.be.null
expect(usuario).to.have.all.keys('nome', 'email', 'password', 'administrador', '_id')
})
});
});
});
context('When I send GET /usuarios passing id query param', () => {
it('Then it should return only the filtered user', () => {
cy.request({
method: 'GET',
url: '/usuarios',
qs: {
_id: '0uxuPY0cbmQhpEz1'
}
})
.should((response) => {
expect(response.status).to.eq(200)
expect(response.body.usuarios[0].nome).to.eq("Fulano da Silva")
});
});
});
});
Keep in mind that we are sending two requests to the same endpoint. This will need to be refactored, but we will come back to this case later.
Moving on to the next HTTP method, we will now create a new file called POSTUsuarios.spec.js. In this file we will put all tests related to the POST method.
Create the test structure using the Mocha functions, exactly as we did in the GET file. Obviously, make changes to the descriptions according to the scenario describe
, context
and it
.
/// <reference types="cypress" />
describe('Given the Users api', () => {
context('When I send POST /usuarios', () => {
it('Then it should create a new user', () => {
});
});
});
This time the cy.request()
function will be slightly different.
POST
./users
(no need to add the complete url, just add the resource).body
param we'll add the necessary information to create a new user. To find out what information is needed, always consult the API documentation at ServeRest.Our payload will look like this:
body: {
nome: "Dumb John",
email: "dumb.john@qa.com.br",
password: "test",
administrador: "true"
}
Let's take the opportunity to validate the success message and status code.
/// <reference types="cypress" />
describe('Given the Users api', () => {
context('When I send POST /usuarios', () => {
it('Then it should create a new user', () => {
cy.request({
method: 'POST',
url: '/usuarios',
body: {
nome: "Dumb Joe",
email: "dumb.joe@qa.com.br",
password: "test",
administrador: "true"
}
})
.should((response) => {
expect(response.status).eq(201)
expect(response.body.message).eq("Cadastro realizado com sucesso")
});
});
});
});
Success! Are you sure? Try to run the test again.
When we run the test again it fails, but why? This is because, according to the business rule, it is not allowed to register a user with an already used email. Let's use a strategy to solve this problem.
Let's edit the file located in plugins > index.js
We will use lib Faker
to create a different user for each request. Install faker
using the npm
command:
npm i -D faker
I named this task
as freshUser()
and inside it we will pass key/value using faker
lib:
freshUser() {
user = {
nome: faker.name.firstName(),
email: faker.internet.email(),
password: faker.internet.password(),
administrador: "true"
};
return user;
}
The complete file will look like this:
/// <reference types="cypress" />
const faker = require("faker");
/**
* @type {Cypress.PluginConfig}
*/
module.exports = (on, config) => {
on("task", {
freshUser() {
user = {
nome: faker.name.firstName(),
email: faker.internet.email(),
password: faker.internet.password(),
administrador: "true"
};
return user;
}
})
return config
}
Now refactoring POSTUsuarios.spec.js
to usefaker
. Create a variable called fakeUser
.
let fakeUser;
In order to create a different payload before each execution, we will use a hook called beforeEach()
. Within this hook, we will call the function cy.task()
throwing user
created by the task to the fakeUser
variable.
beforeEach(() => {
cy.task('freshUser').then((user) => {
fakeUser = user;
cy.log(JSON.stringify(fakeUser))
});
});
I also added a
cy.log()
to see the user being created for each request. REMOVE THIS FROM YOUR CODE.
Change the body parameter to receive fakeUser
variable as well.
cy.request({
method: 'POST',
url: '/usuarios',
body: fakeUser
})
The refactored file looks like:
/// <reference types="cypress" />
let fakeUser;
describe('Given the Users api', () => {
beforeEach(() => {
cy.task('freshUser').then((user) => {
fakeUser = user;
cy.log(JSON.stringify(fakeUser))
});
});
context('When I send POST /usuarios', () => {
it('Then it should create a new user', () => {
cy.request({
method: 'POST',
url: '/usuarios',
body: fakeUser
})
.should((response) => {
expect(response.status).eq(201)
expect(response.body.message).eq("Cadastro realizado com sucesso")
});
});
});
});
We can now run the tests as many times as we want.
I will finish this post now, because we already have a lot of information here!
Please keep in mind that just reading this post will not make you learn. Make your own code, practice, fail and fail again until you succeed!
Feel free to add me on LinkendIn. Leave your comments, questions and suggestions.
Thanks for your attention, see you on next post!!
Posted on May 23, 2021
Sign up to receive the latest update from our blog.