Automated End-to-End Testing with Cypress and Husky Pre-Commit hooks

How you can use Cypress and Husky together to build a pre-commit testing hook for your application

What comes to your mind when you have to write test specifications for your front-end application? Annoyance? Boredom? Well, be annoyed no more because Cypress is here to make your testing lives fun and exciting. So without further ado, let’s get into why Cypress is such a great tool and how we can use it to create our pre-commit hook.

What is Cypress?

Why should I use it?

1. Easy Setup

2. Written in Javascript

Bonus: If you know how to write tests in other Javascript frameworks like Mocha, you will have a smooth experience picking up Cypress since it is based on top of those tools.

3. Comes with a GUI

4. Hot reloading

5. Waits for components to load

6. Ability to go back in time

7. Amazing Documentation

In short, Cypress is a testing framework that not only makes testing easy and fun but also comes out of the box with a lot of features that will make you fall in love with it.

What are the drawbacks?

1. Problems with single-page apps

2. Automated wait time might not be enough

How do I set it up and use it?

npx create-react-app cypress-medium

cd cypress-medium

npm start

We will use react-bootstrap for our components, you can install it using:

npm install react-bootstrap --save

Inside the cypress-medium directory, you can follow the code from my repository to build your own components. Here is a little snippet from that file to show how yours should like:

Note: We are using the useState hook to keep and update the state of our application, you can read more about it here.

Sample React App

To run through this real quick, we are setting our states using our hooks, then declaring if our submit button should be enabled based on the inputs in the form. We have a very basic form that uses components from react-bootstrap, and we also have a modal that will be an additional feature we can test with our framework.

Now that we have our react app set up, we can do what we have been waiting for, installing Cypress! You can use one of these commands in your project’s directory to install:

npm install cypress — save-dev

or

yarn add cypress --dev

Then to initialize Cypress, run the following command:

node_modules/.bin/cypress open

To run your tests in a headless manner, you can use run instead of open. Remember this should all be in your project’s directory for these commands to work.

After running this command you will see a new folder named “cypress” in your project. Cypress will generate a bunch of example files for you within the integrations directory. We will go ahead and delete all of them because we will be building a custom one for our application.

Next up, we are going to update our package.json to override npm test to run Cypress. In your package.json file, under the scripts object, update test to say test: "./node_modules/.bin/cypress open.

Let’s create a new example.js file inside the integration directory. This is where we will write our tests. We will have a very basic test that only goes to our react app.

describe(“Our first test”, () => {  it(‘should go to our sample react app, () => {    cy.visit(“http://localhost:3000/");})}

describe is the convention used to group together smaller tests and it s are supposed to denote the smaller unit tests.

This gives us a nice little demo of a Cypress spec. Now, we can dial it up a bit. Let’s try to select a component and type something in it. We can use the selector tool in the UI to do this. Click the button on the top left, click on the email field, and you will see how Cypress helps you select it.

We can now use this to type something into our text box. Use the following command to implement it:

cy.get(‘#formBasicEmail’).type(‘test’);

Some common methods that will help you test for basic components of your application are:

.should() — check for various thing.click() — click elements.contains() — checks if the DOM contains a string.url() — checks for URL properties

It’s that simple! Now we can build on top of this, and create a more in depth test. Let’s check for form validation logic. In our test, we want to first go to the webpage, check if our submit button is disabled and try some basic form inputs. We can achieve this with the following:

describe(“Our first test”, () => {  it(“should go to our sample react app”, () => {    cy.visit(“http://localhost:3000/");    cy.contains(“Cypress testing with Husky pre-commit hooks”);  });  it(“should see that the submit button is disabled”, () => {    cy.get(‘.submitBtn’).should(‘be.disabled’);    cy.get(‘.modalBtn’).should(‘be.enabled’);  });  it(“should enter some incorrect email value — submit should be         disabled and modal should be enabled”, () => {  cy.get(‘#formBasicEmail’).type(‘test’);  cy.get(‘.submitBtn’).should(‘be.disabled’);  cy.get(‘.modalBtn’).should(‘be.enabled’); }); it(“should enter some password value — submit should be disabled and modal should be enabled”, () => {  cy.get(‘#formBasicPassword’).type(‘test@test.com’);  cy.get(‘.submitBtn’).should(‘be.disabled’);  cy.get(‘.modalBtn’).should(‘be.enabled’); });  it(“should check the box — submit should be enabled and modal should be disabled”, () => {  cy.get(‘#formBasicCheckbox’).click();  cy.get(‘.submitBtn’).should(‘be.enabled’);  cy.get(‘.modalBtn’).should(‘be.disabled’);  });});

Okay so we have a good long enough file, but the beauty about Cypress is that it is extremely easy to read. Anyone reading the above code can understand what exactly is happening.

There is one little problem though; we have quite a few lines that are repeated. The disabled and enabled checks are repeated a few of times because we want to see if our form logic is working as expected. We can refactor these lines using custom commands to reduce the size of our code and make it more readable and reusable.

So to create a custom command in Cypress go to the support/commands.js file. You will see a few commented examples of such commands, for our tests we will name our custom command checkForm

Cypress.Commands.add(‘checkForm’, () => {   cy.get(‘.submitBtn’).should(‘be.disabled’); cy.get(‘.modalBtn’).should(‘be.enabled’);
});

Now we can use cy.checkForm() to do both the checks instead of specifying these everywhere. We reduced our lines of code being used by 50% in our main file. This is a very small example, but imagine reusing these commands for your production app. Custom commands can help reduce your code size significantly and make it easier for another person to follow.

Using the above custom command, and testing out our modal, the final result looks like this:

You might get a cross origin error while executing the above code. We can fix this by adding "chromeWebSecurity": falsein our cypress.json.

Alright, so if you run Cypress with the open option, you should see something like this:

Final Result

Now that we have successfully created our testing framework for the app that we are planning on releasing, we should be able to run these tests before we commit our code. This is where Husky comes in.

To install Husky pre-commit hooks in your application, run the following commands:

npm install husky — save-devnpx husky install

To add our npm test, which we directed earlier to run Cypress, to the hook, all we need is the following line:

npx husky add .husky/pre-commit “npm test”

Now try committing something to your Github repo. You should see the Cypress tests run and check for your app to pass.

One final change would be that I would recommend updating our package.json from:

test: "./node_modules/.bin/cypress open to

test: "./node_modules/.bin/cypress run

when you try committing you should see something like this:

Passing Test
Final Output
Failing Test

And there you have it. You have created an automated end-to-end test which runs on each commit to your repository!

Conclusion

Coupling Cypress with a pre-commit hook, like Husky can make your life a lot easier because you can be confident that your application works the way it was intended to.

Happy Testing!

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store