Setting up Data Before Each Test

Our first test makes sure that a single todo can be added to our application. However, a todo app that only takes a single todo is useless. So let's write a test to make sure our app can add multiple todos.

First, we need to create another it() function to test adding three todos.

describe("React TodoMVC", () => {
  it("adds a single todo", () => {
    cy.visit("http://localhost:8888")
    cy.get(".new-todo").type("Buy Milk{enter}")
    cy.get(".todo-list li").should("have.length", 1)
  })

  it("adds three todos", () => {})
})

Remember, the first thing we need to do is to tell Cypress to cy.visit() to our app. Each it() function is a separate test, so for each new test we write, we have to specifically tell Cypress to cy.visit() our application.

You may start to see a potential annoyance here. Granted, since we only have two tests right now, this is not that big of a deal, but what if our test file had several tests. This would mean that within every single it(), we would have to specifically tell Cypress to navigate to our app for each test, which is very repetitive and annoying. There is a better way, fortunately.

beforeEach()

The beforeEach() function, which is provided by Mocha, is perfect for code that you want to execute before every test. Let's update our test file to use it, like so:

describe("React TodoMVC", () => {
  beforeEach(() => {
    cy.visit("http://localhost:8888")
  })

  it("adds a single todo", () => {
    cy.get(".new-todo").type("Buy Milk{enter}")
    cy.get(".todo-list li").should("have.length", 1)
  })

  it("adds three todos", () => {})
})

Both tests are still passing with this simple update, and we have cleaned up our duplicate code.

Multiple Todos

Now let's update our adds three todos test to add three todos.

it("adds three todos", () => {
  cy.get(".new-todo").type("Buy Milk{enter}")
  cy.get(".new-todo").type("Pay Rent{enter}")
  cy.get(".new-todo").type("Pickup Dry Cleaning{enter}")
})

Great our application can easily handle adding three todos.

However, you may have noticed we are repeating ourselves once again.

it("adds a single todo", () => {
  cy.get(".new-todo").type("Buy Milk{enter}")
  cy.get(".todo-list li").should("have.length", 1)
})

it("adds three todos", () => {
  cy.get(".new-todo").type("Buy Milk{enter}")
  cy.get(".new-todo").type("Pay Rent{enter}")
  cy.get(".new-todo").type("Pickup Dry Cleaning{enter}")
})

Instead of having to manually type out and hard code each and every todo, we can simply put them into a variable. Remember, Cypress is just JavaScript, so let's refactor the names of our todos into constants so we can easily re-use them.

We will create variables at the top of our test, just underneath the describe() block.

describe('React TodoMVC', () => {
  const TODO_ITEM_ONE = 'Buy Milk'
  const TODO_ITEM_TWO = 'Pay Rent'
  const TODO_ITEM_THREE = 'Pickup Dry Cleaning'

  beforeEach(() => {
    cy.visit('http://localhost:8888')
  })

// ...

Then we can update our todos to use these new variables.

describe("React TodoMVC", () => {
  const TODO_ITEM_ONE = "Buy Milk"
  const TODO_ITEM_TWO = "Pay Rent"
  const TODO_ITEM_THREE = "Pickup Dry Cleaning"

  beforeEach(() => {
    cy.visit("http://localhost:8888")
  })

  it("adds a single todo", () => {
    cy.get(".new-todo").type(`${TODO_ITEM_ONE}{enter}`)
    cy.get(".todo-list li").should("have.length", 1)
  })

  it("adds three todos", () => {
    cy.get(".new-todo").type(`${TODO_ITEM_ONE}{enter}`)
    cy.get(".new-todo").type(`${TODO_ITEM_TWO}{enter}`)
    cy.get(".new-todo").type(`${TODO_ITEM_THREE}{enter}`)
  })
})

Much better. Now we can easily re-use these todos throughout our tests.

Finally, let's add an assertion like we did in our first test to make sure only three todo's have been added. Like so:

it("adds three todos", () => {
  cy.get(".new-todo").type(`${TODO_ITEM_ONE}{enter}`)
  cy.get(".new-todo").type(`${TODO_ITEM_TWO}{enter}`)
  cy.get(".new-todo").type(`${TODO_ITEM_THREE}{enter}`)
  cy.get(".todo-list li").should("have.length", 3)
})

And all of our tests are passing. Awesome!

Making our tests more robust

So we currently have two tests, one that adds a single todo and another that adds multiple todos. This is great and useful, but we can take these tests further to make them even more robust. For example, shouldn't we also test that the todos render the correct text within the app? That seems valuable to test, so let's update our tests to do just that.

Let's first try this out by updating our first test, which adds a single todo. Once we get it working, we can add this assertion to the other test.

If we inspect our app with our dev tools again, we want to ensure that the text that we enter into the input field is rendered exactly as one of our todos. We can see that within each <li> each todo name is wrapped in a <label> element.

Knowing this, we can assert that our todo's name is within this <label> element.

First we need to get all of the <li> elements.

cy.get(".todo-list li")

Then we want to make sure we grab the first element in this array. We can do that by using the .eq() method.

cy.get(".todo-list li").eq(0)

We then pass .eq() the index of our element, which should be the first one, and it will return that element from the array.

Then we will use the find() method to find our <label> element.

cy.get(".todo-list li").eq(0).find("label")

The .find() method will look for child elements of a specific selector.

So, we are getting the first <li> element from the .todo-list element and then within that <li> element we are looking for the <label> element.

Finally, we want to make sure that the <label> contains the text of our todo item.

cy.get(".todo-list li").eq(0).find("label").should("contain", TODO_ITEM_ONE)

Save the file and let the tests run again.

Great, everything is green and passing.