Is it possible to use just one tool to test everything? Although it may sound like a developer’s daydream, it is almost possible with Cypress, a JavaScript frontend testing framework. Cypress is built specifically for JavaScript frontend developers, and they can use it to start writing tests quickly without needing to add third-party dependencies or packages. This is a benefit missing from other tools like Selenium. In this tutorial, I will guide you through using Cypress to test an API.
This tutorial covers the following:
- Setting up Cypress framework
- Understanding how Cypress makes HTTP calls to APIs
- Understanding how to use Cypress
cy.request()
method
Prerequisites
To follow this tutorial, you will need to have a few things in place:
- Basic knowledge of JavaScript and Git/GitHub
- Node.js installed on your system (version >= 10.3)
- A CircleCI account
- A GitHub account
Using Cypress for API testing
Because Cypress is a standalone frontend testing tool, it makes HTTP requests on behalf of the web applications as it is executing. While it might appear that Cypress is making these requests from the browser, it actually uses Node.js as an engine to make the HTTP requests to the API server. It uses Node.js to return the responses received from the server.
Note: Cypress exposes the cy.request()
method to make HTTP requests from within the framework. That is the method we will use in this tutorial to test our endpoints using Cypress.
Initializing the directory and setting up Cypress
First, create an empty directory and initialize an empty Node project by running these commands on the terminal:
mkdir testing-apis-with-cypress && cd testing-apis-with-cypress
The first command creates the directory while the second creates a Node.js project with default configurations. We will update the default config later in the tutorial. We will write our tests in the initialized Node project.
npm init -y
You need to know that npm init -y
creates a Node project with the default configuration and will not prompt you to customize the contents of the project’s package.json
file. The package.json
file will yield a different result if only npm init
is run, because it will prompt you for the configuration of the Node project properties.
Now that you have an initialized directory, you can install the Cypress framework. Installation of Cypress happens just like installation of any other npm package, so you can install it with either:
npm install cypress
Or:
yarn add cypress
After Cypress has been installed, the next step is initializing the Cypress framework. This process will create the default Cypress directories that you need to write your tests and include packages, if that is required.
In the terminal, run the command cypress open
. This command will initialize Cypress for the first time and create default Cypress directories and files.
Note: If you run into issues you may need to install Cypress globally.
Setting up Git and pushing to CircleCI
Before you can start writing tests, you need to set up CircleCI. To begin, initialize a Git repository in the project by running the command:
git init
Next, create a .gitignore
file in the root directory. Inside the file, add node_modules
to keep npm generated modules from being added to your remote repository. The next step will be to add a commit and then push your project to GitHub.
Now log into the CircleCI and navigate to Projects. There will be a list of all the GitHub repositories associated with your GitHub username or your organization. Find the repository that you want to set up in CircleCI. In our case it is the api-testing-with-cypress
project. On the Projects dashboard, select the option to set up the selected project. Select the option for using an existing configuration.
Now, on the prompt, select the option to start the building process. You can expect your pipeline to fail, because you still need to add the customized .circleci/config.yml
file to GitHub.
Writing the CI pipeline
Once you have set up the CircleCI pipeline, it is time to add CircleCI to the local project. Start by creating a folder named .circleci
in the root directory. Inside this folder, create a config.yml
file. Add these configuration details to config.yml:
version: 2
jobs:
build:
docker:
- image: cypress/base:14.16.0
environment:
## this enables colors in the output
TERM: xterm
working_directory: ~/repo
steps:
- checkout
- restore_cache:
keys:
- v1-deps-{{ .Branch }}-{{ checksum "package.json" }}
- v1-deps-{{ .Branch }}
- v1-deps
- run:
name: Install Dependencies
command: npm ci
- save_cache:
key: v1-deps-{{ .Branch }}-{{ checksum "package.json" }}
# cache NPM modules and the folder with the Cypress binary
paths:
- ~/.npm
- ~/.cache
- run: $(npm bin)/cypress run
- store_artifacts:
path: ~/repo/api-testing-with-cypress
In this configuration, CircleCI pulls a Cypress Docker image from the environment and checks for any stored cache of our dependencies.
The next stage is to restore the cache if it exists, and update the application dependencies only when a change has been detected with save-cache
. It runs the api-testing-with-cypress
Cypress tests and stores the cached items in the artifacts that are in the ~/repo/api-testing-with-cypress
directory.
Push your changes to GitHub and CircleCI should automatically start the building process. Here, since we currently do not have any tests yet, our pipeline will fail again. We will re-run this later on after we have added our tests. On the CircleCI dashboard, you should be able to see the created pipelines and the execution even without running tests in our repository.
Getting started with Cypress
For this tutorial, we will use an already built API that we have hosted on heroku to test our application. To do this though, we first need Cypress to understand what is the API Url that we will be using in our tests (Cypress refers to this as the baseUrl
).
Setting thebaseUrl
Cypress framework has a configuration file cypress.json
that is generated by the Cypress initialization process. The cypress.json
file stores all the configuration variables that we supply, and this is where we will set our baseUrl
. This is to ensure that we do not need to repeat the entire URL when we are running our tests. The baseUrl
will be set as a configuration variable to our Heroku API URL as shown by the code snippet below in the cypress.json
file.
{
"baseUrl": "http://todo-app-barkend.herokuapp.com/"
}
Setting the run command
Now that we have our test URL ready, we need to create an easy way for us to run our tests using the terminal. To do this, we will add a script in the package.json
file in the scripts section. To achieve this, we will need to add the test
command below to the package.json
file scripts section.
"scripts": {
"test": "cypress open"
},
This command tells Cypress to run in non-headless mode and open a browser when executing our tests.
Scripts in the package.json
file automate commands and scripts that you would have had to run on the terminal. Instead you use npm
to execute them while packaging them in a way that is understandable to the program being run.
Writing tests in Cypress
Cypress is a frontend testing tool, and it generates a lot of files and directories that are examples of how to write Cypress tests. For now we can ignore them.
Now that Cypress has been initialized and your CircleCI pipeline has been configured, it is time to start writing tests.
Writing your first Cypress API test
Remember, Cypress works by using Node under the hood to trigger HTTP requests and route them back to Cypress to make assertions and to verify that the expected calls were made. In the first test, we will make a GET
request to our todo application to make sure that it actually returns the todo items to the API. Before we do this, we need to delete everything in the directory cypress/integration/
. This directory contains the dummy scaffolds generated by Cypress initialization. In cypress/integration/
, create another directory named api-tests
. To make sure Cypress understands the actions of the test folder, update cypress.json
file with this configuration value.
{
d "integrationFolder": "cypress/integration/api-tests"
}
In the api-tests
folder, create the todo.spec.js
file, which is where we will write our tests. Here is the awesome bit: Cypress is self-sustaining, so we do not need any other external dependency to test our APIs.
Open the todo.spec.js
file to set up the test on the API GET
request for the todos. Add this code:
describe('TODO api testing', () => {
let todoItem;
it('fetches Todo items - GET', () => {
cy.request('/todos/').as('todoRequest');
cy.get('@todoRequest').then(todos => {
expect(todos.status).to.eq(200);
assert.isArray(todos.body, 'Todos Response is an array')
});
});
});
In this example, Cypress attaches baseUrl
to the cy.request()
method. This method makes a GET request to the hosted API endpoint to get our todo items, and validates that the API is being called by the test.
Note: When cy.request()
has not been passed any HTTP request operation, it defaults to a GET request. That is why you do not need to tell the test that you are making an API GET
request.
Executing Cypress API tests
To execute the first test and check that everything is running properly, run the command npm test
in the terminal. We defined the npm test
command earlier in our package.json. The Cypress dashboard will open.
Any added test specs (files) will show up on the Cypress dashboard when we run the command to execute tests. Select the actual test and watch Cypress execute it.
The dashboard shows that test execution was successful and we were able to get all our todo items. Cypress runs in a browser by default, so we can further verify all the elements that were returned from the test.
From the left side of the screen, click the last assertion that shows the returned arrays. This prints the output to the console. You can then right-click on the inspect element on the same window to open the browser developer tools. Click the Console tab to display the array of items that we are asserting in our test. There is a reason to celebrate, because we can verify that our API and our tests are working correctly.
Writing and adding more tests
Now that you are familiar with adding Cypress, you can add more tests. For example, you can test adding a new todo item and deleting a todo item.
it('deletes Todo items - DELETE', () => {
cy.request('DELETE', `/todos/${todoItem}`).as('todoRequest');
// deletes Todo item with id = 9
cy.get('@todoRequest').then(todos => {
expect(todos.status).to.eq(200);
assert.isString(todos.body, 'todo deleted!')
});
});
it('Adds Todo item - POST', () => {
cy.request('POST', '/todos/', { task: "run tests" }).as('todoRequest');
// adds new Todo item by defining Todo name
cy.get('@todoRequest').then(todos => {
expect(todos.status).to.eq(200);
cy.wrap(todos.body).should('deep.include', {
task: 'run tests',
completed: false,
});
});
});
In this code snippet, we declare the type of HTTP request in the cy.request()
. That is also where to declare any other arguments like the body
for the POST
request that creates a new todo item. Using these tests, you can verify that you can create a todo item using our API, get all the todo items created, and delete a todo item. Success! Our API is not broken.
Now we need to verify that the tests are also passing on the already created CircleCI pipeline. You have already initialized CircleCI, so you just need to commit and push your changes to GitHub. CircleCI will automatically run our tests.
Again, success! All your have tests passed.
Conclusion
In this tutorial, you have set up Cypress and configured it to run API tests. You learned how to set the baseUrl
for testing and configured the directories in the testing framework. You learned to use cy.request()
to make different HTTP operations and how to use the Cypress test runner. I hope you and your team can benefit from what you learned in this tutorial. Enjoy developing your applications!
Waweru Mwaura is a software engineer and a life-long learner who specializes in quality engineering. He is an author at Packt and enjoys reading about engineering, finance, and technology. You can read more about him on his web profile.