This site runs best with JavaScript enabled.

Playing with experimental network stubbing feature in Cypress

Filip Hric

September 28, 2020


In the beginning of September, Cypress released a new experimental feature called experimentalNetworkStubbing. I was watching development on Github for a while and was really excited when I started seeing some rapid movement on the issue. I decided to have a closer look into what it does. I share code examples here, but if you want to play with this I have put together a quick and dirty repo. Clone → npm install, → npm start → npx cypress open and you’re good to go.

With version 5.1.0 or higher, you can enable this feature by adding following line into your cypress.json file:

1{
2 "experimentalNetworkStubbing": true
3}

This enables you to use .route2() command which is described in Cypress documentation. Imagine .route() command, but on steroids. You’ll see in a minute.

The power of .route()

If anyone ever asked me about my favourite command in Cypress, it would be .route(). With a simple syntax you can watch your api call being made:

1cy
2 .server()
3 .route({
4 method: 'GET',
5 url: '/todos'
6 }).as('todoslist');
7
8cy
9 .visit('/'); // open page
10
11cy
12 .wait('@todoslist'); // items load from server via api

After our app is opened, it loads a list of items from database. To do that, it calls a GET request to the /todos url. Response comes back as a simple json file which is then rendered in our app. If you want to change this response and provide your app with your own json list of items, just add another parameter:

1cy
2 .server()
3 .route({
4 method: 'GET',
5 url: '/todos',
6 response: 'fx:items' // fixtures/items.json
7 }).as('todoslist');
8
9cy
10 .visit('/'); // open page
11
12cy
13 .wait('@todoslist'); // items load from server via api

This is simple, yet very powerful thing you do to test your application. .route() command enables you to look into any xhr request your application makes and test it. You can combine your api and ui tests into one.

The problem with this command though, is that you can only work with xhr requests. This rules out fetch requests, or other assets loaded via network. If you tried to route fetch request, you would end up like this:

The power of .route2()

With .route2() command you can route fetch requests just as you would do with XHR. Pretty neat.

1cy
2 .route2({
3 method: 'POST',
4 path: '/todos'
5 })
6 .as('createTodo');
7
8cy
9 .visit('/');
10
11cy
12 .addItem('new todo item'); // fetch request fired when adding item
13
14cy
15 .wait('@createTodo'); // it works!!

That’s not all though. You can route static files such as css or images. This can become super handy if you want to test a website with lazy loaded images:

1cy
2 .route2('/vendor/index.css')
3 .as('css');
4
5cy
6 .route2('/vendor/cypress-icon.png')
7 .as('logo');
8
9cy
10 .visit('/');
11
12cy
13 .wait('@css')
14 .wait('@logo');

But there’s more! With .route2() command you can not only change response of your api call, but also request itself. Let’s say we want to add a custom header to our request to let the server know that these are coming from application that is being tested at the moment. You can manipulate your request headers like this:

1cy
2 .route2({
3 method: 'POST',
4 path: '/todos'
5 }, (req) => {
6 req.headers['Mr-Meeseeks'] = 'Look at me!!';
7 })
8 .as('createTodo');
9
10cy
11 .visit('/');
12
13cy
14 .addItem('new todo item');

This new header cannot be observed in network panel in devtools, because the request manipulation actually happens outside of browser - before the request is sent to the server. Because of that, devtools show the original request headers. But in the terminal where you ran your npm start command, you can see that I’m logging all request headers for POST /todos request and our newly added header is visible there:

1{
2 connection: 'keep-alive',
3 host: 'localhost:3000',
4 'proxy-connection': 'keep-alive',
5 'content-length': '61',
6 'user-agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.121 Safari/537.36',
7 'content-type': 'application/json',
8 accept: '*/*',
9 origin: 'http://localhost:3000',
10 'sec-fetch-site': 'same-origin',
11 'sec-fetch-mode': 'cors',
12 'sec-fetch-dest': 'empty',
13 referer: 'http://localhost:3000/',
14 'accept-encoding': 'gzip',
15 'accept-language': 'en-GB,en-US;q=0.9,en;q=0.8',
16 'mr-meeseeks': 'Look at me!!'
17}

In the same way we have changed our headers, we are able to change request body. Our application takes title of todo item from our text input. Once we hit enter key, that input is sent via fetch request to our server. When using .route2() we can actually change what is being sent to server, or even add our own data.

1cy
2 .route2({
3 method: 'POST',
4 path: '/todos'
5 }, (req) => {
6 const requestBody = JSON.parse(req.body);
7
8 req.body = JSON.stringify({
9 ...requestBody,
10 title: 'Wubba Lubba Dub Dub!'
11 });
12 })
13 .as('createTodo');
14
15cy
16 .visit('/');
17
18cy
19 .addItem('new todo item');

All these examples can be found in a repo that I have put together for this blog. Feel free to play around with it and let me know on twitter, what you think of this new feature. In my perspective Cypress team has done amazing job here, and I’m excited about possibilities this change will bring.

EDIT: Gleb Bahmutov from Cypress wrote a really cool blog on differences between .route() and .route2() commands, where he demonstrates some cool stuff you can do with .route2(). You should definitely check it out.

Share article
Cypress test automation course
If you like my articles, you’re going to love my new course on Cypress.io. It’s called Cypress test automation for people in a hurry, and it is exactly what you’d expect. A compact, fast and straight-to-the-point course with lots of practical examples and challenges. Check it out!

More articles coming! Get them to your inbox ✉️



Filip Hric © 2020