🌟 Help others discover us, star our GitHub repo! 🌟

Mobile Menu

Testing protected app sections the right way with Cypress custom commands!

Adrian SmijuljTwitter
December 19, 2018

With Cypress, testing web apps has finally become super easy! It is precisely what I was looking and hoping for back in the day when I was still using Selenium. It is a tool that doesn’t require multiple (often confusing) steps to setup and offers everything you’ll (ever) need to get the job done. There is really a LOT of goodies packed in it so, basically, if by any chance you haven’t tried it yet, do it now! 😊 🚀

Starting Out With Cypress

For my first experiment, I decided I would start with signup and login flows of my app. And, basically, after an hour or so of playing around, I already had the whole thing done. Now that was fast 😼! Sure, there were a few things to learn along the way but, overall, I’d say everything was pretty straight forward.

Next Steps

Satisfied with my progress, I decided to move onto the sections that are protected or, in other words, sections which cannot be accessed without an authenticated user. At that point, the question that immediately popped up in my head was:

Is it possible to have an already logged-in user before a test is initiated? This is a “must” if I want to be able to open any protected part of the app.

I had a few initial ideas; however, they weren’t good enough.

1. I’ll Simply Continue Where I Left Off in the Same Signup / Login Flow Test.

This is practically useless since as the test grows, it’ll get harder and harder to run and maintain it. Imagine yourself testing something and each time you update the test, you have to wait for the signup, login, and additional steps in-between to be executed. Not very nice, right? And you cannot really execute a specific test, it’s all or nothing. I discarded this one very quickly.

2. I’ll Create New Tests and Rely on the User Created in the Initial Signup Flow Test.

Based on my previous testing experience, I knew this was considered bad practice. Ideally, tests should not be linked together; they should be written and executable independently, because that way it’s easier to run and maintain them. If I want to run a single test, I should always be able to do that. And, as I make changes to the initial signup flow test, I should not have to worry about the fact that I might break the other ones. So, this one was a “pass,” too.

3. I’ll Just Sign Up and Log in Users at the Beginning of Each Test.

The idea here was to simply repeat the signup flow steps (UI interaction) in each test, after which I would get a fresh user and then be able to move and test different parts of the protected area. Although this would theoretically be the most correct approach, still, in each test I would have to wait for the signup flow to be completed. And, if I had 20+ similar tests, that would seriously impact the overall execution time.

max 1000 02oqE2rbug25BDa7P

The last idea was definitely the best out of the three. But again, we can’t accept the seconds that would be added to the execution time in each test. So, how can we solve this problem?

Test Signup / Login Flows Only Once

The key thing to recognize here is that once we test the signup and login flows, we don’t want to repeat the same steps (UI interaction) in other tests, because it’s simply redundant.

So, instead of going through the signup / login flows again and again, we could actually achieve the same thing MUCH faster by simply manually replicating background actions that would be triggered while the user is completing signup and login flows. Specifically in our case, we actually want to send a few API calls and save the received authentication token to the browser’s local storage. Once again, it’s okay to manually replicate these actions, because signup and login flows were tested in their own tests.

Cypress Commands to the Rescue

Did I mention that Cypress offers everything you’ll need out of the box? Well, it certainly does! 🎁 😊

Cypress comes with a lot of built-in commands. E.g. to send a simple HTTP request to your backend API, you can utilize the cy.request command like so:

cy.request({ url: "/api/users", method: "POST", body: {something: "xyz"} }).then(({ body }) => { // Do something. });

But that’s not all! On top of built-in commands, Cypress also lets you define custom ones, with which you can easily execute a frequently used series of commands. Exactly what we need!

New custom commands can simply be registered via cypress/support/commands.js file, like the following:

Cypress.Commands.add("doSomething", () => { // Do something });

Once you’ve done that, you’ll be able to call cy.doSomething() anywhere in your tests. Cool, huh? 😎 Just make sure to give descriptive names to your commands so that the code is more readable at later stages.

Registering a New Custom Command

So, in our case, we want to register thecy.signupUserAndLogin custom command, which will do the following (steps may vary depending on how your app is structured):

  1. Create random data for our new user
  2. Send the signup API call
  3. Send the login API call
  4. Save the received authentication token to the browser’s local storage

These steps are represented by the following code:

Cypress.Commands.add("signupUserAndLogin", () => { // 1. Create a random data for our new user. const user = generateRandomNewUser(); // 2. Send sign up API call return cy .request({ url: "/api/users/signup", method: "POST", body: user }) .then(({ body }) => { // 3. Send log in API call cy.request({ url: "/api/users/login", method: "POST", body: { email: user.email, password: user.password } }).then(({ body }) => { // Save received auth token to local storage window.localStorage.setItem("authToken", body.authToken); return body.data; }); }); });

Note that we’ve put both the signup and login functionality into one command. If necessary, you might want to create separate logInUser and signUpUser commands first in order to trigger only a specific action when needed.

With the custom command registered, you can now execute it in any test where an authenticated user is required. E.g., if we were to test the user profile form, we may end up with something like this:

describe("user profile", () => { it("displays the user profile form", () => { cy.signupUserAndLogin().then(user => { // We have user ready at this point. // Now redirect to desired URL, and make the necessary assertions. cy.visit("/user-area/my-profile") .get(".someFormElement") .should("exist"); // ... }); }); });

As you can see, we called the custom command at the beginning of our test and, as soon as it was done, we redirected the browser to the user profile form page and made the necessary assertions. And, that’s it! You can also repeat the same steps in other tests where an authenticated user is required.

max 1000 0YRyZt7FtsktyVBp6

Conclusion

With custom commands, testing protected app sections has certainly become a lot easier! We’ve eliminated the need to repeat the same signup / login steps and, with that, our tests have not only become easier to read (because of fewer lines of code) but also take less time to execute.


Thanks for reading! My name is Adrian and I work as a full stack developer at Webiny. If you have any questions or comments, feel free to reach out to me via Twitter.

About Webiny

Webiny is an open source serverless CMS that offers you all the enterprise-grade functionalities, while keeping your data within the security perimeter of your own infrastructure.

Learn More

Newsletter

Want to get more great articles like this one in your inbox. We only send one newsletter a week, don't spam, nor share your data with 3rd parties.

Webiny Inc © 2024
Email
  • We send one newsletter a week.
  • Contains only Webiny relevant content.
  • Your email is not shared with any 3rd parties.
Webiny Inc © 2024
By using this website you agree to our privacy policy
Webiny Chat

Find us on Slack

Webiny Community Slack