Whether you're testing your UI or API, Cypress gives you all the tools needed to work with and manage network requests. This intermediate-level task demonstrates how to use the cy.request and cy.intercept commands to execute, spy on, and stub network requests while testing your application in the browser. Learn how the commands work as well as use cases for each, including best practices for testing and mocking your network requests.
Hello, I'm Cecelia Martinez and I am a technical account manager at Cypress, and I'm really excited to be here today to talk to you about network requests with Cypress. I've made these slides for the presentation available at the URL on the screen, and you can connect with me on Twitter @ceciliacreates.
What is Cypress?
[00:38] So for those of you who aren't familiar with Cypress, it is a free open source framework that allows you to write and execute tests for any application that runs in a browser. Cypress does execute tests against your application inside a real browser. You can see on the screen here, a representation of our graphical user interface when you're running your test locally. So the application under test is on the right hand side in the app preview. And then on the left hand side, we have what's called our command log, which as it sounds like, is a log of all the commands that your test code is executing against your application.
This gives us some good information about network requests that are happening inside your application as the test are progressing. So you can do network requests as they occur in the command log. Here in the screenshot, we can see that after our test code has clicked an element that a post request occurred. We can see the status code. We can see the endpoint as well as how we may or may not be interacting with that request via our test code. You can also click on any request in the command log to output it to the console. This gives you some additional helpful information like request headers, response body, which can be useful when you're troubleshooting or debugging failed tests or better trying to better understand what's happening with the network requests from your application.
Commands today that allow you to work with network requests
[02:00] So in addition to viewing network requests within the Cypress test runner, you can also interact with them. So we're going to be talking about two commands today that allow you to work with network requests within your Cypress tests. The first is cy.request, which executes HTTP requests. And the second is cy.intercept, which allows you to interact with network requests. For demonstration purposes today, I'll be using some code from the Cypress real world app. The Cypress real world app is a great resource. It was developed by our developer experience team. It's a full stack react application that showcases best practices, use cases, and then essentially all the different things that you can do with Cypress. So it is open source. So if you go to the GitHub link on the screen, you can view the source code as well as a full suite of UI and API test for the application. It's a Venmo style payment application. So you're dealing with things like users, transactions, bank accounts, and a lot of very familiar UI components.
How it works
[03:04] All right, so we can get started with cy.request. So as I mentioned, cy.request sends an HTTP request from your test code. This is separate from the functionality of your application. It's similar to how you may send an HTTP request from a curl or postman. So the syntax is cy.request. At minimum, you do have to pass through the URL of the endpoint, where you're making the request. You can also pass through the method, a specified body and then additional options for the request.
[03:38] So why would you use cy.request? A most popular use case is actually for API testing. So Cypress, while it's mostly known for UI and end to end tests, you can also use the cy.request command along with the same assertion language that you're familiar with in order to validate responses coming back from your server endpoint.
So in the example here, this is coming directly from the API test suite in our Cypress real world app. We are validating a get request to our bank accounts endpoint, and the con the it block for the test is that it gets a list of bank accounts for a user. So in this case, we're using cy.request online for, in order to make a get request to that endpoint. And then we are grabbing the response and we're making assertions against it. So we're expecting that response status will equal 200 and we're expecting that the response body will contain the user ID in the results. So again, you can leverage the same Cypress syntax that you're used to for API testing alongside your UI test suite. You can also use it to retrieve, create, or update data for testing. So test data management is a really popular topic when it comes to testing, especially when you're doing end to end tests. And so if you need to retrieve data from an endpoint, such as an username or password to log in, or if you need to create a transaction or create something in your application in order to test it, rather than doing it programmatically, you can use a cy.request call in order to do that via your API.
[05:15] You can also use it to validate endpoints before proceeding with your test. So if you have maybe an unreliable endpointer backend, that might be slower or down, or if you're using a third party service or a microservice, and you just want to ensure that everything is all set before proceeding with your test, you can fire off a cy.request, validate that it's response status is 200, that it's good to go. And then go ahead and proceed with the test that requires that endpoint.
How it works
[05:46] All right. So that was cy.request. Now let's dive into cy.intercept. Cy.intercept is a very powerful, very flexible command. There's lots of things that you can do with it. So we have quite a bit to dive into, but first let's talk about how it works. So this image is from a presentation called how cy.intercept works, my glove bomb top. And essentially, you can see in the image here, Cypress routes all HTTP request through a proxy. So it allows you to essentially literally intercept any request going in and out from your application of the browser, to the internet. So any request can be observed, what we call spied on or stubbed. And then it also provides you with a route handler that allows for really dynamic, a really controlled matching of which requests you'd like to intercept, which we'll be talking about right now.
Network Spying. Declaring a spy with cy.intercept()
[06:44] So first let's dive into network spying. So you can declare a spy with cy.intercept. You want to do this as early in your test code as possible because you want to declare the spy on the network request before the network request actually happens. So through the cy.intercept command, you can pass a URL, a method, or a route matcher. If no method is passed, then any method type will match. So if you just pass through an endpoint, then Cypress will intercept, get request, post request, pass request, whatever it is that takes place to that endpoint. So this is going to be the syntax that we're going to look at. Like I said, you can do a URL, a method, or a route matcher. So let's go ahead and start with, just start out cy.intercept, pass an endpoint. Again, any method type will match to that endpoint.
You can also specify the method itself and you can use a route matcher. So route matcher is an object used to match which HTTP requests will be handled. So it's kind of like a mixing match, grab bag of different optional properties that you can leverage in order to be really, really granular around which requests that you'd like to intercept. So, in our example, here we are leveraging the URL and the query properties in order to say, we only want to intercept request to this URL that have a query property that matches the expected term. And again, you can see that how you can be very, very specific by leveraging whichever properties that you need that give you the specificity that you're looking for.
Aliasing a spy with .as()
[08:24] So once you've declared an intercept, you want to be able to leverage it and you do that by aliasing it with .as. So it allows you to save the intercepted request to use throughout your test code. So what that looks like is, so in the example that we just talked about, we have cy.intercept, we're passing through our route matcher, and we're saying any network request that matches this route, that matches this criteria, we want to save that and we want to give it a name of search. So now we will have essentially this alias route, this alias request called search that we can leverage throughout our test code. Common use case for that is waiting. So you can wait for a request to occur with cy.wait.
Waiting for a request with .wait()
[09:09] So after you declare the intercept and after you've given an alias, then you can use cy.wait to wait for that request to occur anywhere in your test code. So in this example, here, we have cy.intercept, we're saying any get request to our users' endpoint, please intercept that and save it as get users. Then anywhere in our test code, we can trigger something, click on small elements, have a grand old time, and then we can wait and say cy.wait('@getUsers'). And that's going to wait for that request to occur for real, from our application before proceeding with the test code.
[09:49] All right. So the examples that we talked about so far are essentially static matching, right? So it's criteria that you've established in advance when you're defining that route. You can also do dynamic matching. So this is really useful specifically for aliasing GraphQL requests. So the Cypress intercept command has some additional methods that are made available via the API. So bypassing that req option through to cy.intercept, it gives us access to those API commands. One of them is req.alias. So req.alias kind of replaces .as to allow you to alias route only if it matches the criteria that you're programmatically defining. So let's take a look at what I mean, in our example, we're saying cy.intercept, please intercept any post request to our API GraphQL endpoint. If you're familiar with GraphQL, you typically have a single endpoint that handles lots of different types of queries and mutations.
So in this case, we're going to pass through that req property in order to access some API options so that we can do a little bit of programmatic interception here. So we're going to go ahead and grab the body of any of those requests. We're going to define that. And we're going to say if the body has a query property and that query property includes list bank account then, and only then, do we want to go ahead and alias that request as GraphQL list bank account query. So this allows us to, again, dynamically, grab a request, say, hey, does this request actually have what we need? Yes? Okay, great. Now I'm going to go ahead and name that. And again, that's happening at the time of requests because essentially we're grabbing a request that's taking place. We're parsing through the body, seeing if it matches what we need to match, and then we're going to go ahead and alias that. So again, really, really useful for a GraphQL request specifically.
[11:50] So once you've defined your intercept, you've given it a name, you've waited for it, one of the most common use cases and is then you can also make assertions about it, right. So you can do that the same kind of similar way that we did for our API request. So in this example, we're going to go ahead and still use our get users endpoint that we've interception, that we've declared. We're going to go ahead and wait for it. And then we're going to do some assertions on it. So we can either use chain style where we're saying it's request URL should include users. This can be certain things that are available for it, like request. body, request.status that you can just chain directly off of, or you can use a dot then and then grab the interception in order to access any of the lower level interception properties. So that would be the request, the response, the headers, status, everything that you could possibly need. And then you can use that same expect assertions and tax to make sure that the request that went out or the response that came back was actually what it should have been to validate the behavior of your application.
And then last but not least, you can also wait for multiple requests. So for example, if you are still using get user's route, maybe the first time it goes through, it should include user one, but then we have some additional test code, we're creating another user. And then the second time it goes through, it should include user two. So we're using the same alias, but we're waiting for the request to occur multiple times because it could be different each time.
[13:30] All right. So that was network requests. Again, we talked about intercepting, aliasing waiting and then making assertions on requests going to, and from your application. Now let's talk about network stubbing.
So network stubbing allows you to essentially bypass the real response of your network and say, give it something else instead. It allows you to essentially trick the UI or the front end of your application. So you can pass the response body to cy.intercept to stub the actual network response. There's a lot of different options for this. I'm going to go through some of the more popular ones.
[14:06] The first is you can leverage a Cypress fixture file. So Cypress allows you to essentially have static assets that you can pass through in your test code. So, and this example here, we're saying cy.intercept anything that's going to our /users.json endpoint, go ahead and stub that out using the users.json fixture that we have alongside our test code. You can also stub the response with a json body, just one that you've written yourself that should match whatever the expected response is from the server. And then you can also essentially create a body that leverages certain properties. So if you wanted to define a status code, body, headers, you can do that with an object as well. Those are all static responses.
[14:55] Just like how we could dynamically match routes, we can also dynamically stub. Just like we had req.alias. We have req.reply, which is another method or function that can be used to send a stubbed response for an intercepted request. You can pass a string, an object, or a static response. A static response, it's like an object type that gives you some additional options. If you want to do things like forcing network errors or delaying or throttling the response, but it is essentially for everything else if you a string or an object doesn't make sense, or if you want some additional options.
So in our example, here, again, we're intercepting to the billing endpoint. We're going to pass through req so we have access to those API methods, and then we're going to dynamically get a billing plan name at request-time. So again, this is something that we can do dynamically. As we're intercepting that request, we then are grabbing the data and then we're going to reply with that data and stub that response. So this can be, as you can see, there's a lot of flexibility here. But if for some reason you just aren't able to define that information in advance, or if you need to grab something from your UI, or if you need to validate data before you stub the response out, you can do that dynamically using the req.reply function. So again, the object will automatically be json stringed and sent as the response, instead of the real server response for that request.
Handling multiple requests
[16:37] You can also handle multiple requests. Same way you can wait for multiple requests, you can handle multiple requests and stub them doubt differently. So as the versions have 7.0, we have this option. And then the request handlers supply to intercept will be matched, starting with the most recently defined interceptor. So this allows users to override the previous stub by calling it again. So in our example, here, we've declared an intercept to our login endpoint. We're stubbing it out with username one, and we're saving that as login
Let's go ahead. Then we'll have some test code here and then we're going to wait for that login request to occur. And it should have username one because that's our stubbed response. Then we're declaring the intercept again, this time passing username two. This is going to override that first intercept. Again, we're saving as login again, we're using the same alias. And then this time after we've done some test code to trigger that response, we are going to go ahead and wait for it again. And this time it's going to have username two, because that second cy.intercept has again, overridden the first one. So we're able to stub responses multiple different ways throughout our test code, depending on what we need. Very cool stuff.
Pros and Cons of network stubbing
[17:56] All right. So we talked about network stubbing. Let's talk about pros and cons of it. When would you want to do it? When wouldn't you want to do network stubbing?
Approach: Real Server Responses
[18:05] So the first approach is leveraging your real server responses. So this is a true end to end test. This is not leveraging network segment at all, letting all of the real API, all the real server responses come back and going through your test that way. It does guarantee that the contract between your client and your server or your front end and your back end is working correctly.
So some of the pros, right, it's more likely to work in production. So your test is going to better mimic your actual production environment. And then, provide better assurance that your tests are going to work the same way that your application's going to work. And that you have more confidence that the user's going to have the experience that they expect. You do get test coverage around server endpoints. And it's great for traditional server side HTML rendering. It gets really hard to step out a lot of HTML if you aren't using real server responses.
[19:02] There are some cons, right. So it requires seeding data. If you're using a real backend, you're going to need real data. It's definitely going to be much slower because you're completing real network requests. You have to wait for the API to respond. It's also harder to test edge cases because you have to force those edge cases via the UI. You have to force those edge cases to occur via the application rather than creating via stub responses.
So a suggested use is to kind of use sparingly. It's great for the critical paths of your application when you really need that true end to end coverage.
Approach: Stub Server Responses
[19:35] The next approach is to stub your responses. This is essentially enables you to control every aspect of the response. As we saw a cy.intercept, you can stub any which way that you like, and it's a great way to control the data that is returned to the front end of every application.
Pros, you obviously you get that control. You can force some certain response behavior like network delays. You don't have to worry about code changes to your server. So you can even also start testing the front of your application before the server or the backend's completed.
And it's also much, much, much, much faster, but obviously you're not going to have guarantees that your stub data matches the actual data. You're not going to have test coverage on some server endpoints, and it's also not as useful for a traditional server side HTML rendering, right.
[20:23] So the suggestion here is to mix and match, have one true end to end test for those critical paths and then everything else stub it out. Give yourself a much faster test week, give yourself that control that you need.
All right, this has been network request with Cypress. Again, I'm Cecilia Martinez. Thank you so much for your time. And I hope this is helpful for you.
[20:43] Richard Bradshaw: What a fantastic talk. Clearly, a very versatile tool. So before we dive into the Q and A though, let's bring Cecelia on and have a look at the poll results. Hey Cecilia. So you asked people how many tech conferences have we attended. Before we look at the results, how many of you attended?
[21:01] Cecelia Martinez: Oh, I'm definitely in that more than four category. For me, virtual conferences like this have made me have made it really easy to attend a lot more of them. So I think I've probably lost count at this point, to be honest.
[21:13] Richard Bradshaw: And what do you think about the results?
[21:16] Cecelia Martinez: Yeah. Okay. So most people look like they're in that one to three conferences. Quite a few people that it's their first though, too, which is pretty exciting. Chose a good one and coming here and checking it out. So it looks like we do have some people kind of earned that more than four category as well, but maybe not quite as many.
[21:37] Richard Bradshaw: I'm the same as you. Being online has opened up so many doors for speakers for different types of events. So I've been to a lot, but it's all different at the moment, isn't it? So we've got some questions for you and I'm not surprised because from your talk, let's call it a speech can be used for so many things. So we're going to start with Malcolm who is asking, how do we log the request responses or can we log the request responses?
[22:06] Cecelia Martinez: Yeah, absolutely. So there's a couple of different options, right. So you can use actually the cy.log command that allows you to essentially log that to the Cypress command log. If you're looking to log that to your output as well, we have some options there. There's some different various tools that you can use. You can also always kind of grab those responses and leverage them in different ways if you needed to, for example, document them in the output and to validate that. So cy.log is probably going to be the most user friendly initial aspect of it. But there's also some additional options depending on where you want to log those responses to.
[22:43] Richard Bradshaw: Fantastic. We've got a question from Refactor Eric, is it advisable to test the frontend of the entire backend API mopped out, of course, accompanied with a few real end to end for the critical user journeys?
[22:58] Cecelia Martinez: Yeah. So I think that this goes back to talk earlier about how there's not really a one size fits all approach or a solid good answer either way. It all depends on the context, right, of what you're trying to do. So when I talk to users all the time, I say, what is your goal for that test? And what is your goal for your test suite and what you're trying to approach? So if you are looking to test the functionality of your frontend and you don't care about the backend, then absolutely it is totally fine to mock out your backend completely. I see that implementation a lot. People are like, we're using microservices in the backend, or it's a completely different team and we just want to ensure that our piece is functioning properly.
If your goal is to ensure that you have the entire end to end working properly, then yes. I use that approach a lot as well, where you have some critical user flows covered with end to end testing. Then you have some separate API testing for the endpoints that are highly leveraged that maybe have complex business logic that you want to do some unit testing on and ensure that's working to cover all the edge cases. Then you have your mocked out front, front end UI tests as well to ensure that everything looks pretty, maybe throw some visual testing in there to ensure that it's all rendering properly. It's a very valid approach. But again, the answer's always going to be, it depends because there's always going to be context around your goals.
[24:18] Richard Bradshaw: Yeah. And if I can add on, I think the key thing for me is the word risk. What's the risk that you are trying to mitigate? Is the risk in the UI? Is it the full UI end API? Once you identify where the risk is, it usually answers itself about the best approach. But, again, you are showing that Cypress is diverse and you can handle multiple ways. Fantastic. So Test the JS has got another question. Oh no, sorry. I've missed one out. So Harlow, there are a lot of ways to check that a request has been sent, but there isn't seem to be a recipe to check that a request wasn't sent. What's the best way to do that in Cypress?
[24:58] Cecelia Martinez: So there are ways to do it. So when people ask me, how can I do this? How do I do that? A lot of times I'll ask them why they're trying to do that, because-
Richard Bradshaw: That's what I'm thinking.
Cecelia Martinez: Because I can answer your question. Yeah. There's a couple different ways. It's not easy for sure. It's always hard to prove an untrue or to prove something didn't occur, right. It's always prove this didn't happen. One of the ways that I've seen it is declaring that route and then you do it really a custom function to set the count, the number of times that it's occurred. And if it stays zero, then you can kind of see that hasn't happened. But really, if you are having a check that something hasn't occurred, then likely there's something really non-deterministic about that test. And maybe some understanding needs to take place around the requirements of your application and what you're testing against, because if a network request happens and it shouldn't happen and you need to just assert that it hasn't happened, I would kind of challenge that use case and understand more about what you're trying to accomplish with that. So technically it's possible, but really question why you would want to do that.
[26:09] Richard Bradshaw: Awesome. Yeah. Couldn't agree more. I've seen it sometimes in mobile. You want to see the kind of responses that are being sent because you kind of do I trust this UI? Do I trust this website? But I agree with you. There's a positive that I would want to look at. So why are we concerned that it was set at some point. There's so many questions to ask. Awesome answers. So test the JS. I am back to you now. So some time ago I had a really big challenge in Cypress to access the length of two lists because properties like length are only accessible by a dot then. Hence, to access list from another list you had to end up with a chain of dot then and then a chain of ifs, which is not very clean. It took me a lot of time and I couldn't find a better way to do it. Any tips? That makes no sense to me. I hope it makes sense to you.
[26:57] Cecelia Martinez: So I probably would get a little bit more detailed, but I know that you can also leverage dot ifs to get properties off of a subject. And then the other thing to keep in mind too, is if you are trying to get the length of something and then maybe get into it again, not sure, kind of what the use case would be there. It's kind of hard to understand without seeing the code, but a couple things that kind of are in my mind, right, is if you're using really good selectors and you're able to tap into whatever that second list is, so to speak, or if you are able to kind of be a little bit deterministic about what you're getting. It sounds like maybe what's happening is somebody is selecting a first list, getting its length in order to understand what element on that list that they need to get. And then trying to get another element within it.
Maybe. It's a little bit tough to understand, like I said. You can definitely use that. You can also kind of use maybe a dot children command in order to kind of see what children are available off of that list and parse it out that way. But I always recommend again, is kind of having really good selectors and understanding what element that you're trying to target. So you don't have to do as much traversal. That being said, it's not always easy. If you don't have access to the source code, if you, if it's going to be a really non-deterministic rendering, that's where having good test data management comes into play, where you're able to kind of know what's going to be rendered on your application and your test environment and can be more deterministic that way. But I would say, take a look at dot ifs and take a look at some of the maybe dot children, if you are having to do a lot of traversal, because those maybe be more flexible for you than dot then.
[28:35] Richard Bradshaw: I do understand the question now. I thought lists was a feature in Cypress. He generally means a list on the website.
Cecelia Martinez: I think that's what he is talking about. Yeah.
Richard Bradshaw: Or they. Have no idea who they are, what they may be talking about. Excellent. Okay. So Millard has our next question and also news for you. They started using Cypress three months ago. They have a problem though, when I use it, which is I can't handle lazy loading. And the only option I know is the weight command, but the lazy doesn't load components in the exact amount of time. Do you have any recommendations for handling lazy loading?
[29:08] Cecelia Martinez: Yeah. So this is something that we're seeing increasingly right with, with kind of modern front end frameworks that use leverage lazy loading, or for example, sometimes you need to scroll into view in order to trigger it to load. So actually, I believe maybe Gleb recently did a blog post or a video about this in order to essentially it was results that were being dynamically loaded onto the page. And so with search results, they'd scroll down, wait for it to load, scroll down, wait for it to render. And there's also a couple other things that you can do too. If you're using it frontend store like UX, or if you're using Redux, you can kind of tap into your frontend store and essentially force things to trigger, to load something. So if you know what action that gets dispatched in order to render that component, you can go ahead and dispatch that manually the Cypress in order to force that to happen.
And then there's also always going to be essentially asserting that the dom is ready. And so lasy loading can be difficult, but if you understand what will trigger the load to that component, then you can really work with that in order to ensure that it's going to appear on the screen, whether that be a scroll, whether that be a click. Sometimes I've seen people where they'll type in the button, but then what they click on isn't actually the element that fires the event. So they're just kind of not grabbing the right element. So a lot of that is just really understanding your application and what is causing those components to load.
[30:37] Richard Bradshaw: Yeah. I couldn't agree more. Understanding the state, once you know the state. And also sometimes are you trying to test a lazy loading? Can you turn it off because sometimes these things are just there to be nice to have, a bit flare, bit of style. But if you're not trying to test the flare and style, turn it off, make your life easy, make it easier to test these obligations.
[30:57] Cecelia Martinez: That's a really great point, Richard, for sure. What are you trying to test, right? And if it doesn't need to be tested, then maybe just don't enable that in your test environment. So that's a good point.
[31:08] Richard Bradshaw: Exactly. Question from Mandalore. Hopefully, a relatively simple one I reckon. But can you stop HTTPS request from responses?
[31:16] Cecelia Martinez: Yeah, absolutely. Yeah. So it is actually, I think one of the options in that dynamic matching that I mentioned is for HTTPS to only match the secure requests.
Richard Bradshaw: Perfect.
Cecelia Martinez: I believe.
[31:31] Richard Bradshaw: Jobs is asking us, is it possible to start a general intercept method and call it throughout different tests with a slight modification to the variables?
[31:43] Cecelia Martinez: Yeah. I would recommend something like a custom command for that. So essentially in Cypress you can declare custom commands. So I would recommend essentially passing through, if you run a certain query parameter and you call it cy.intercept with query and then you could pass through whatever the query parameters are and then have the cy.intercept declared in there. But absolutely.
[32:03] Richard Bradshaw: Excellent. We might have this time for this one more from Malcolm is there a beginner's documentation to someone new to Cyprus. Is there a beginner documentation to someone new to Cyprus?
[32:14] Cecelia Martinez: Yeah. So docs at Cyprus that I own. That's how I learned Cypress when I was first starting out, for sure. Also, test automation university is a free platform that has a couple of courses on Cypress. They have a beginner course and then one of our Cypress ambassadors, they just really stay more advanced course as well on test automation university.
Richard Bradshaw: There's also one on this website anyway.
Cecelia Martinez: Awesome. That's even better.
Richard Bradshaw: Thank you so much, Cecelia. Really enjoyed that talk. Fantastic answers to the Q and A. So thank you for joining us.
Cecelia Martinez: Yeah, absolutely. Thank you so much. And I hope everyone has had a great event.