Lesson 11

The First Tests

Creating the first tests for our application

PRO

Lesson Outline

The First Tests

Right now we just have a blank application that doesn't do much of anything. However, we've got everything that we need for tests configured and ready to go and we have a bit of background knowledge about testing, so let's get started.

In a previous lesson we discussed how to choose what to test first, and we ended up deciding on this requirement:

  • A user should be able to view a list of modules for the course

Seeing this requirement, if we were not using Test Driven Development we might start working on the template for our home page, or maybe we would create a provider to handle loading in modules. However, writing code without a test is a big no-no in TDD, so we are instead going to start with a test.

With that in mind, we could still just go create a provider, set up a unit test for it, and start working on loading in some modules. But as we have discussed, unit tests alone are not sufficient to test an application as a whole. An E2E test is a good way to test that the various components in the application are working together as intended, and they are also a great way to enforce the requirements of the application. We will be sticking to the methodology of creating an E2E test first to drive out the unit tests.

Let's create our first E2E test now. Since this is the first E2E test we are creating we are going to go through it in a lot of detail. Throughout this process, let's keep the steps in mind from the Test Development Cycle lesson:

  1. Write an E2E test for some specific requirement
  2. Check that the E2E test fails
  3. Based on the failure you get from the E2E test, decide what functionality needs to be worked on in order to get it passing
  4. Write a unit test for the functionality you have decided that you need to create to get the E2E test to pass
  5. Check that the unit test fails
  6. Implement the code to satisfy the unit test
  7. Check if the E2E test passes now
  8. If the E2E test passes, go to Step 1. If the E2E test fails, go to Step 3.

Create an E2E Test

We are at Step 1 now, so the first thing we will need to do is create an E2E test.

Create a file at cypress/e2e/home.cy.ts and add the following:

describe('Home', () => {
  beforeEach(() => {
    cy.navigateToHomePage();
  });
});

Before we even add a test, this is the basic structure for our spec file. We have a beforeEach block that will be run before each test that we create in this file. This block should effectively "reset" our test to the state it needs to be in to be run independently from any previous tests.

We are making a call to:

cy.navigateToHomePage();

If you have already added this code, you might notice that we are attempting to make a call to something that does not exist. What we want is a way to make sure we are on the home page before we run our tests, so we are going to make use of that support folder to add a custom command that will navigate to the home page automatically for us.

This isn't an issue now because the home page is the only page, and we could easily just add something like:

cy.visit('/');

to our beforeEach. However if the way we get to the home page changes in the future (and it will), we would need to go and manually update every single beforeEach that navigates to the home page manually. Instead, if we create a custom command, we can just update it in the one place.

Add the following to the cypress/support/commands.ts file:

declare global {
  // eslint-disable-next-line @typescript-eslint/no-namespace
  namespace Cypress {
    interface Chainable<Subject> {
      navigateToHomePage(): typeof navigateToHomePage;
    }
  }
}

const navigateToHomePage = () => {
  cy.visit('/');
};

Cypress.Commands.add('navigateToHomePage', navigateToHomePage);

export {};

This might look a bit intimidating at first, so let's explain it a little bit. Technically, all we need to do to create a custom command is this:

Cypress.Commands.add('navigateToHomePage', () => {
  cy.visit('/');
});

But then when we try to call cy.navigateToHomePage() the IntelliSense in the IDE is not going to know that this method actually exists and it is going to cause us problems. To deal with this, we use the template that Cypress provides us for adding the method to the Cypress namespace which will provide the appropriate type information to make our code editor happy.

Once you have added a custom command, you will also need to make sure to import the commands file inside of the index.ts file.

Uncomment the following import in cypress/support/e2e.ts:

import './commands';

Now that we have the navigateToHomePage() command defined that our beforeEach block will use let's add our first real test.

Modify cypress/e2e/home.cy.ts to reflect the following:

describe('Home', () => {
  beforeEach(() => {
    cy.navigateToHomePage();
  });

  it('should be able to view a list of modules', () => {
    cy.get('[data-test="module-list-item"]').should('have.length', 5);
  });
});

We have added a test to check that a list of modules is visible on the page. Specifically, there should be 5 modules in the list. To test this we use cy.get to grab a reference to an element that matches the selector [data-test="module-list-item"] and then we chain on the should method to check that it has a length of 5 (i.e. cy.get was able to find 5 elements that matched that selector).

NOTE: We can use any CSS selector we want for cy.get but to conform with best practices we will use data-* attributes to select elements for our test. This will allow us to attach an attribute like data-test='module-list-item' to an element, and then select that in the test. This makes our tests less brittle, because if we rely on selectors like classes (e.g. .module-list ion-item) we might inadvertently break our tests when refactoring our code.

Of course, that list doesn't exist yet (nor should it) so how do we properly set up a reference to it? You just guess! Grab a reference to the element in the way you want it to work. At this point in time, I think the modules will likely be displayed as a list, and I will wnt to grab items within that list. It doesn't matter if this actually ends up being how it works, you could be completely wrong or maybe you change your mind and come back to change this later - it's just a place to start.

Now we could write our test like this, but we are going to make a bit of a modification. We wrote a custom navigateToHomePage command so that we didn't have to worry about updating tests with changing navigation implementations in the application. We have a similar problem here. If we are using this selector in multiple tests, we don't really want to have to keep repeating ourselves.

We could write custom commands for this too if we wanted, but we are going to use a different pattern instead for grabbing references to elements. We are going to set up a utils file with helper function to grab references to elements for us.

Create a file at cypress/support/utils.ts and add the following:

export const getModuleListItems = () =>
  cy.get('[data-test="module-list-item"]');
PRO

Thanks for checking out the preview of this lesson!

You do not have the appropriate membership to view the full lesson. If you would like full access to this module you can view membership options (or log in if you are already have an appropriate membership).