Transcripted Summary

Up until now we had just one test, and we learned a lot about how to use Cypress commands like cy.get, .click, and .type to test our app.

But now it’s time to talk about the tests themselves — how to create more than one test, and how to organize them in groups.

Let’s start by looking at the first test.



As we can see, it’s actually testing 3 things:

  1. adding a new todo;
  2. toggling a todo; and
  3. clearing completed todos.

Let’s start by separating them into 3 tests.

First, we’ll create three tests.

  • Should add a new todo to the list
  • Should mark a todo as completed
  • Should clear completed todos



We’ll delete the original one later. The new tests are currently empty.

First let's copy the “add todo” code to the first test.


it('should add a new todo to the list', () => {
  cy.visit('http://todomvc-app-for-testing.surge.sh/')
  cy.get('.new-todo', {timeout: 6000}).type('Clean room{enter}')
  cy.get('label').should('have.text', 'Clean room')
  cy.get('.toggle').should('not.be.checked')
}) 

Second, we'll copy the test logging logic of the toggling to the second test.


it('should mark a todo as completed', () => {
  cy.get('.toggle').click()
  cy.get('label').should('have.css', 'text-decoration-line', 'line-through')
}) 

And third we'll copy the clear completed to this one.


it('should clear completed todos', () => {
  cy.contains('Clear completed').click()
  cy.get('.todo-list').should('not.have.descendants', 'li')
}) 

There we go.

We'll delete the first test. And we're good to go — 3 tests separated.

Let's run a test:

npx cypress open



Yes, it works!

Notice how nicely Cypress shows the 3 tests? As you can see, I can open each one separately.

When you have 3 tests then it's very easy to understand each one, but after a while, you will want to group your tests in logical groups.


# Grouping Your Tests

Tests in Mocha are usually grouped around ‘describe’ groups.

Let's group these three tests together. We'll create a describe group which is actually just a function call, as usual, and call it “todo actions”.

It's very similar to an it — it has a name and a function. What we need to do is put in the three it's, it tests inside the group describe.


/// <reference types="cypress" />

describe('todo actions', () => {
  it('should add a new todo to the list', () => {
      cy.visit('http://todomvc-app-for-testing.surge.sh/')
      cy.get('.new-todo', {timeout: 6000}).type('Clean room{enter}')
      cy.get('label').should('have.text', 'Clean room')
      cy.get('.toggle').should('not.be.checked')
  })

  it('should mark a todo as completed', () => {
      cy.get('.toggle').click()
      cy.get('label').should('have.css', 'text-decoration-line', 'line-through')
  })

    it('should clear completed', () => {
      cy.contains('Clear completed').click()
      cy.get('.todo-list').should('not.have.descendants', 'li')
    })
})

This is how we group tests in Mocha.

There, we've now grouped tests in one group.

It is common and customary to group tests in groups, both for aesthetic reasons, and for reasons we’ll see in a few minutes.


A nice feature Mocha gives us is the ability to run only one test, and not all of them.

How?

We add a .only to the it, which will make Mocha run only that test.

Let's try that. Let's run only the first test by adding it.only.



it.only says run only this test.

Let's run it.

See how Cypress runs only that one test?



There we go. It ran only the first test. We're good.

You can add more than one .only to the file and it will run all the it tests that have .only.

Now if we added another .only, it'll run 2 tests, but that's fine. What happens if we want to run only the second test?

Will it work? We’ll move the .only from the first test to the second test.

Let's run it.

Horror!



The test failed!

Looking at the second test, it’s pretty obvious what happened.

The second test doesn't have the prologue of visiting the site and adding the first todo. It assumes that the first test ran. This is a problem. The tests are not dependent.

Sometimes this is what you want, especially if the setup time for a test is long, but usually let's try and make the tests not depend on one another.


The simplest way to make the second test independent of the first would be to copy the prologue.

And the prologue is the visit and the get. Let's copy the two lines.


  it('should mark a todo as completed', () => {
      cy.visit('http://todomvc-app-for-testing.surge.sh/')
      cy.get('.new-todo', {timeout: 6000}).type('Clean room{enter}')
      cy.get('.toggle').click()
      cy.get('label').should('have.css', 'text-decoration-line', 'line-through')
  })

[This means that the second test will visit the page and add the todo. Then it will do the additional actions.]

Let’s run the test. And, voila! It works.

But this isn't a good approach. There’s code duplication, and that’s bad in this context.


But there’s a solution to that — it’s called beforeEach.

Anything we put inside a “beforeEach” will be executed before each test in the group.

Note about beforeEach

beforeEach is a function of Mocha, and it accepts another function. This function will be executed before each of the tests. And as we’ve already learned, the “() => {...}” is an “anonymous” function.

Let's try it.


/// <reference types="cypress" />

describe('todo actions', () => {
  beforeEach(() => {
    cy.visit('http://todomvc-app-for-testing.surge.sh/')
    cy.get('.new-todo', {timeout: 6000}).type('Clean room{enter}')
  })

  it('should add a new todo to the list', () => {
    cy.get('label').should('have.text', 'Clean room')
    cy.get('.toggle').should('not.be.checked')
  })

  it('should mark a todo as completed', () => {
      cy.get('.toggle').click()
      cy.get('label').should('have.css', 'text-decoration-line', 'line-through')
  })

    it('should clear completed', () => {
      cy.contains('Clear completed').click()
      cy.get('.todo-list').should('not.have.descendants', 'li')
    })
})

There we go. We took the things that we want to run before each test, and we added them inside this anonymous function that the beforeEach will run.

Let’s try that.

There we go. It passes.

But we just ran the one. Let’s remove the .only and ensure all the tests run together.

Let's try running them all. There we go.



It didn't pass.

And why didn't it pass? Because this third is now dependent on the toggle click in the second test.

Should we move it [the toggle click] to here [in the beforeEach block]? I don't think so. I think we should copy. In this case, I think copying makes sense because checking that it should clear completed todos's actually means toggling at least one.


it('should clear completed', () => {
    cy.get('.toggle').click()
    cy.contains('Clear completed').click()
    cy.get('.todo-list').should('not.have.descendants', 'li')
})

Let's run that.

There we go. All the tests pass. The tests now will work independently and there's no duplicate code.

Note that this beforeEach code will run only for the tests running inside that describe group. If there are tests that are outside this describe group, they will not be affected by this beforeEach.

This is great. Describe groups are not only for aesthetic reasons, but they can actually group things logically so that we can add beforeEach and afterEach and before and after to the thing.


Let’s add some more tests.

But this time, not around actions but around the filtering capabilities of TodoMVC — the ability to filter based on completed, not completed, and all.

And we'll group them together, but this time, not by a separate describe group, but this time as a separate file.

So, let's open the Explorer and we'll create a new file. In this file, we’ll create tests that are about filtering the todo list, so we called it “todo-filtering.spec.js”.

While we’re here, let’s rename “todomvc.spec.js” to “todo-actions.spec.js” to better describe it.

Let’s create the tests.

First, we create the describe group called “filtering”.

The describe group needs to be filled with tests. But all these tests check filtering, so let’s create 3 todos so that we’ll have something to filter with.

We need to do that for each test, so we’ll create a beforeEach to do that. We can remove the timeout we used before, it’s not really necessary.


/// <reference types="cypress" />

describe('filtering', function() {
  beforeEach(() => {
    cy.visit('http://todomvc-app-for-testing.surge.sh/')
    cy.get('.new-todo').type('Clean room{enter}')
    cy.get('.new-todo').type('Learn JavaScript{enter}')
    cy.get('.new-todo').type('Use Cypress{enter}')
    cy.get('.todo-list li:nth-child(2) .toggle').click()
  })

Perfect! And let’s also toggle the middle todo.

This time we can't use this selector, .toggle, because we have lots of check boxes with the same class, so we'll have to be much more interesting — `('.todo-list li:nth-child(2) .toggle'. So, I go to the “todo-list”, I go to the second “li” and the “toggle”. Standard selector stuff.

We've toggled the middle page.


Now let’s add the test that clicks on the “Active” button filter, and check that it shows only the uncompleted todos.

We first add an empty it test. Now let’s click on the “Active” button.

Let’s find the “Active” button and click it.


it('should filter "Active" correctly', () => {
cy.contains('Active').click()
})

We’re using cy.contains instead of cy.get because it's much easier. And we're clicking on the active button.

Now let's try it. Let's run it.

There we go.



As you can see, it filtered everything.

We haven't validated it yet, but at least we can see that it works, and you see the “Active” button is clicked on, is checked.


Now we have to check that the todo list has two items only.

The todo list is an <ul> HTML element with class .todo-list, with an <li> html element for each todo shown.

So, we need to check how many elements the todo list has. Let's do that.


cy.get('.todo-list li').should('have.length', 2)

We find all <li> elements in the todo-list, and use should to check that the result has a length of 2.

There we go. We have our first test, and it should run. Let’s see.



Boom! The test ran perfectly.


Let’s create the two additional tests, which also check the “Completed” button and the “All” button.

We now have three tests. Let’s just change the expected length of the list in each test to the right length.


# todo-filtering.spec.js

/// <reference types="cypress" />

describe('filtering', function() {

  beforeEach(() => {
    cy.visit('http://todomvc-app-for-testing.surge.sh/')
    cy.get('.new-todo').type('Clean room{enter}')
    cy.get('.new-todo').type('Learn JavaScript{enter}')
    cy.get('.new-todo').type('Use Cypress{enter}')
    cy.get('.todo-list li:nth-child(2) .toggle').click()
  })

  it('should filter "Active" correctly', () => {
    cy.contains('Active').click()
    cy.get('.todo-list li').should('have.length', 2)
  })

  it('should filter "Completed" correctly', () => {
    cy.contains('Completed').click()
    cy.get('.todo-list li').should('have.length', 1)
  })

  it('should filter "All" correctly', () => {
    cy.contains('All').click()
    cy.get('.todo-list li').should('have.length', 3)
  })
})

And we’re done.



And it works!

Is there a way to run all the tests in all the files?

That’s what we’ll learn in the next lesson.



Resources