Over a million developers have joined DZone.
{{announcement.body}}
{{announcement.title}}

Start Your End-to-End Testing With Angular and Cypress

DZone's Guide to

Start Your End-to-End Testing With Angular and Cypress

In this blog post, we will cover how to get started with end-to-end testing using Cypress and Angular and the AngularCLI.

· Web Dev Zone ·
Free Resource

Bugsnag monitors application stability, so you can make data-driven decisions on whether you should be building new features, or fixing bugs. Learn more.

In this blog post, we will cover how to get started with end-to-end testing using Cypress and Angular and the AngularCLI.

The code for this blog post can be found here.

As a long-term AngularCLI user, I am very used to working with Protractor when it comes to end-to-end testing. To be honest, I did not like end-to-end testing that much. So I was looking for an alternative and a tweet from Dominic Elm pushed me more into the direction of cypress.

So I started taking a closer look.

Preparation

Cypress is an end-to-end testing utility which can run beside your maybe existing protractor tests. To install it just run the following command and it persists to your package.json.

npm i cypress --save-dev

As your application has to run while Cypress is testing it, I use a package called concurrently to run two commands in parallel. In this case, I want to start my application and Cypress.

"cypress:open": "concurrently \"ng serve\" \"cypress open\""

If you now start the Cypress tests, you will notice that cypress created a folder “cypress” for you. In there is a folder called “integration” which is holding your tests.

This is just the default folder. You configure a lot in the Cypress variables. Just check them out and play around with them. The folder is quite interesting in the beginning because it provides a good starting point watching the file “example_spec.js” which contains some example tests.

The Application

The application is rather simple and is just a Todo-List which we want to test end-to-end. We have a form component which can throw the output of an added todo and a list component which can mark items as “done” and expects a list of items as input.

Todoapplication

<p>
  <app-todo-form (onAddTodo)="addTodo($event)"></app-todo-form>
</p>
<p>
  <app-todo-list [items]="items" (onMarkAsDone)="markAsDone($event)"></app-todo-list>
</p>

Every item gets a button beside it which can be clicked to mark the item as “done.” When an item is marked as “done” we will apply a CSS class called “inactive” on it.

<s *ngIf="item.done" class="inactive"></s>

So even if it is a small todo application we have:

  • an input which we can type something in.
  • a button which is not active when the input is empty.
  • another button for each todo item to mark it as done.
  • a todo list containing all the items no matter if they are marked as done or not.

So this is a nice scope to get started with Cypress. So let's go and write some tests for first checking only the title.

<head>
  <title>CypressTest - TodoApp</title>
  <base href="/">
  <!-- ...-->
</head>

Writing Tests With Cypress

So let’s check if the title is correct as our first test. We can wrap all our tests in an describe method which takes a string and another method where we can implement the tests.

The well known “it” method then is wrapping our test directly. First of all, we have to visit our site because, otherwise, we would not have anything to test. Once loaded, we can access the main properties via the “cy” object which Cypress provides us.

describe('My First Test', () => {
  it('Application has the correct title!', () => {
    cy.visit('http://localhost:4200');
    cy.title().should('include', 'TodoApp');
  });
}

Notice that we write tests like we would describe them (well…sort of). We will get to that point later.

However, now we know how to visit a site and how to check properties…let's access some controls. Because that is the most interesting part. We can access controls like we would do with jQuery with an id, for example. So the button

<button id="myControlId">Button</button>

can be accessed with

cy.get('#myControlId');

Let's check if our “Add Todo” button contains the words “AddTodo”

<button id="addtodobutton" [disabled]="form.invalid">Add Todo</button>
it('Button has correct naming', () => {
  cy.visit('http://localhost:4200');
  cy.get('#addtodobutton').should('contain', 'Add Todo');
});

We do not want to write that cy.visit('http://localhost:4200'); everytime, so let's put that in a beforeEach at the top of our tests.

describe('My First Test', () => {
  beforeEach(function() {
    cy.visit('http://localhost:4200');
  });

  // ...`


  it('Button has correct naming', () => {
    cy.get('#addtodobutton').should('contain', 'Add Todo');
  });
});

Let's also check if the button is disabled if nothing has been typed in yet:

it('Add Todo button is disabled when input is empty', () => {
  cy.get('#addtodobutton').should('have.attr', 'disabled');
});

Again we can write tests like we would speak it.

Let’s take that a little bit further and get some things chained with Cypress. Because getting something and testing its state is not the only thing we can do.

So let’s test to see that if we get the button first it has the disabled attribute. Then let's get the input, write something in it, and then get the button again to check if the disabled attribute disappeared.

it('Add Todo button is enabled when input is not empty', () => {
cy
  .get('#addtodobutton')
  .should('have.attr', 'disabled')
  .get('#todoinput')
  .type('SomeTodo')
  .get('#addtodobutton')
  .should('not.have.attr', 'disabled');
});

Again we can chain many of the attributes and methods for which we can find attributes, and assert things with. That is pretty easy to read, isn’t it?

Let’s get the todo, type something in, submit the completed form, and see if the input was reset. And all that with chaining the operators.

it('Submit Form should clear Input', () => {
  cy
    .get('#todoinput')
    .type('SomeTodo')
    .get('#addtodoform')
    .submit()
    .get('#todoinput')
    .should('have.value', '');
});

When getting the input we can type something in with the method type(‘myText’) and then get the form the same way and simply submit it. Then we can check the value of the input again and check if it is empty.

Let's next test if when we:

  • get the input.
  • enter something in the todo input.
  • get the form.
  • submit the form.
  • grab the list items which…
  • … should then contain one item.

If we break up a test like this we can nearly overtake it 1:1 in our Cypress test. Take a look:

it('After submitting form list should contain element', () => {
  cy
    .get('#todoinput')
    .type('SomeTodo')
    .get('#addtodoform')
    .submit()
    .get('#todolist>li')
    .its('length')
    .should('be.eq', 1);
});

To be complete, let's check if the item gets the correct CSS class appended if the “done” button was clicked.

it("After clicking 'done' the item should contain done css class", () => {
  cy
    .get('#todoinput')
    .type('SomeTodo')
    .get('#addtodoform')
    .submit()
    .get('#doneButton')
    .click()
    .get('#todolist>li s')
    .first()
    .should('have.class', 'inactive');
});

What I personally like is that you can step through your tests via virtual snapshots and you can see all the different steps. Besides that, I want to mention that you can always take screenshots at a specific place in your test with the screenshot method of Cypress.

Please check out the complete API here.

todo-application-snapshots

I hope I could give a sneak peek of how easy it is to write end-to-end-tests with Cypress.

Cheers

Fabian

Monitor application stability with Bugsnag to decide if your engineering team should be building new features on your roadmap or fixing bugs to stabilize your application.Try it free.

Topics:
web dev ,end-to-end testing ,angular ,web application testing

Published at DZone with permission of

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}