I want to show you something cool I have been working on: cypress-magic-backend. It is a simple Cypress plugin for automatically recording and replaying API network calls during end-to-end tests. I think this plugin allows the holy grail of fast web testing: run the tests super quickly by removing the real API backend completely.
Let's take an example application cypress-magic-backend-example. It is a TodoMVC application. You load the front-end application, the front-end code loads the data by making API calls to the endpoint /todos
. The website makes lots of network calls:
- the HTML page itself
- the CSS styles resources
- the JavaScript scripts
- an XMLHttpRequest API request to load the todo items
The last XMLHttpRequest network call is the difficult one; all other requests can be served by any static HTTP server (nginx, apache, etc). The API call GET /todos
is the hard one, since it requires executing server logic and possible database access and even calls to other APIs.
This GET /todos
API call is the one returning the data.
Serving static HTML, CSS, and JavaScript resources is simple. Many platforms even allow serving such static resources for free (think GitHub Pages). Running API servers is much harder; you need to execute the actual server code. This makes it difficult and sometimes slow to run end-to-end tests, since you need the real API, the real backend during testing.
Enter cypress-magic-backend. You probably do need the real backend while writing the end-to-end test. In my example application, I have started the local application and opened Cypress. Let's write a test for adding a new todo item.
1 | beforeEach(() => { |
Run the tests
Great, the test is working, but it feels slow. Of course, our local / dev / staging API server is underpowered compared to the production. A typical situation, don't you think? No problem. Our test makes the same API calls during the test, and those calls like GET /todos
and POST /todos
are slow; each takes 1 second.
So... we want to do two things:
- Remove the need to run API server while testing our frontend logic
- Make the test much much faster
Let's do our magic backend thing. We want to mock all calls to /todos
API endpoint. Let's go into cypress.config.js
and configure our magic backend
1 | const { defineConfig } = require('cypress') |
We are only interested in every call to /todos
endpoint, so we define it in apiCallsToIntercept
following the same syntax as any cy.intercept command. Tip: network call interception uses minimatch library under the hood. You can even test the match expression in your Cypress browser!
Let's load the cypress-magic-backend plugin from our E2E support file
1 | // https://github.com/bahmutov/cypress-magic-backend |
Good. Now let's click the "Record" button "🪄 🎥" the plugin adds to the top of the Command Log. It simply runs the spec again, but this time it intercepts and saves all observed network calls into a JSON file
The saved JSON file is unique per spec + test title. In our case it has 4 API calls:
- the initial
GET /todos
returns an empty list of todos - adding the new todos via
POST /todos
calls. Each call sends the Todo object with its title, completed, and id fields. - the last
GET /todos
call after the page reloads
1 | { |
Once the spec has been recorded, click the Replay "🪄 🎞️" button.
Notice the test became much faster; it went from 6 seconds to 2 seconds because all GET
and POST
calls to /todos
were mocked. We no longer need the real API endpoint, we simply need the frontend to do the same API calls, which it should. We can make the test even faster. When replaying the backend calls, there is no need to reset the items. Thus we can skip the cy.request
call at the start
1 | beforeEach(() => { |
The test can check the current backend mode by fetching it from the Cypress.env
object and control its behavior.
The test is now super fast, since ALL api calls are mocked and the initial cy.request /reset
is skipped. We went from 6 seconds to 500ms. You can see all stubbed network calls since they have the 🪄 🎞️
alias.
Great, how do we use it?
- We record all specs ourselves running the tests locally or on CI once per day.
- We commit the
cypress/magic-backend
folder with JSON files into the source control. - When we want to run fast, we simply click the
🪄 🎞️
button - On CI, we can set the replay mode via Cypress env option
Here is GitHub Actions workflow executing both jobs in parallel. One job records the API calls, another simply replays them.
1 | name: ci |
The npm run start:static
script runs a static server without /todos
resource API endpoint. Can you get which spec execution corresponds to the record-mode
vs playback-mode
job?
Nice and fast.
Wait, what does the 3rd button do?
Stay tuned, it is something really special!
👨🏻⚖️ I have released cypress-magic-backend under a dual license. If you are an open-source project, a non-profit, or a for-profit company under 100 employees, feel free to use the plugin for your testing needs. If you are a commercial company with more than a 100 employees and individual contractors, you need to buy a license to use the plugin, even internally. The license fees allow me to work on this and many more testing plugins.