Building out Core Functionality
Adding more tests to cover our core requirements
PROModule Outline
- Source Code & Resources PRO
- Lesson 1: Introduction PUBLIC
- Lesson 2: Introduction to Test Driven Development PUBLIC
- Lesson 3: Testing Concepts PUBLIC
- Lesson 4: Jest and Cypress PRO
- Lesson 5: A Simple Unit Test PRO
- Lesson 6: A Simple E2E Test PRO
- Lesson 7: Introduction to Angular's TestBed PRO
- Lesson 8: Setting up Tests with Jest and Cypress PUBLIC
- Lesson 9: Test Development Cycle PRO
- Lesson 10: Setting up Cypress & Jest in an Ionic/Angular Project PRO
- Lesson 11: The First Tests PRO
- Lesson 12: Injected Dependencies & Spying on Function Calls PRO
- Lesson 13: Building out Core Functionality PRO
- Lesson 14: Testing Asynchronous Code PRO
- Lesson 15: Creating a Mock Backend PRO
- Lesson 16: Setting up the Server PRO
- Lesson 17: Testing Integration with a Server PRO
- Lesson 18: Testing Storage and Reauthentication PRO
- Lesson 19: Refactoring with Confidence PRO
- Lesson 20: Conclusion PRO
Lesson Outline
Building out Core Functionality
So far, we've successfully implemented two of our original six requirements, and we are now up to the last requirement that we considered to be part of the "core functionality":
- After selecting a specific lesson, the user should be able to see content for that lesson
In order to implement the E2E test for this, we will be creating a new E2E spec file. It doesn't particularly matter where you put your E2E tests, technically speaking they could all be in one giant spec file. However, if a test starts on a particular page then we should create a new spec file for that page. There are a couple of benefits to doing this:
- It keeps your tests more organised
- It allows you to more easily arrange your tests with the
beforeEach
function if all of the tests start from the same page
We will also be creating some additional helper functions and a custom command because there will be a third page (the Lesson
page) thrown into the mix.
Create a New E2E Spec File
Let's start by creating a new E2E spec file for our LessonSelect
page. Before we start working on the requirement that we will be focusing on this lesson, we are going to add in another E2E test related to the work we did in the last lesson. You may remember that for our Home page we added an additional E2E test to make sure that there was content in the list of modules (i.e. that it wasn't just a list of blank items, which would technically pass the original E2E test). We are going to do the same now for the LessonSelect
page, and check that the list of lessons also contains some kind of text content.
Create a file at
cypress/e2e/lesson-select.cy.ts
and add the following:
import { getLessonListItems } from '../support/utils';
describe('Lesson Select', () => {
beforeEach(() => {
cy.navigateToLessonSelectPage();
});
it('the list of lessons should contain the titles of the lessons', () => {
getLessonListItems().first().should('contain.text', 'lesson1');
});
});
We will run the E2E tests again, but you might notice now that since we have two different spec files we can choose to run all of them or just the one spec file we are interested in now (the lesson-select.cy.ts file). Our tests run pretty quickly at the moment so it won't really matter too much either way, but you might prefer at some point to just run the tests you are currently working on, and then every now and then run all of the tests to make sure nothing unexpected has broken elsewhere.
After running the E2E tests, we will get the following error:
Timed out retrying after 4000ms: Expected to find element: [data-test="lesson-list-item"], but never found it.
The test failed, as we might have expected, but probably not for the reason you expected. We knew the list contained blank items, but this error is telling us that it couldn't find the list at all.
This is because our navigateToLessonSelectPage()
command is not set up correctly. Currently, it is just pointing to the root URL, but that means the test would be starting on the Home page.
There are two ways we could approach implementing the navigateToLessonSelectPage()
method. Since we are using Angular routing and we know that our routes expect an id
, we could just navigate directly to the /module/1
URL or the /module/2
URL. Alternatively, you could take a more behaviour driven approach and instead simulate the steps required to actually get the application to the lesson select page (e.g. load the home page and trigger a click on a lesson). I don't think there is necessarily a "right" way here, just go with whatever makes more sense for your tests. Personally, I find it gives me greater confidence if my E2E behave as close to the way a real user would behave as possible.
We're going to modify our navigateToLessonSelectPage
command to navigate to the page just like a user would through the application.
Modify the cypress/support/commands.ts file to reflect the following:
import { getModuleListItems } from '../support/utils';
declare global {
// eslint-disable-next-line @typescript-eslint/no-namespace
namespace Cypress {
interface Chainable<Subject> {
navigateToHomePage(): typeof navigateToHomePage;
navigateToLessonSelectPage(): typeof navigateToLessonSelectPage;
}
}
}
const navigateToHomePage = () => {
cy.visit('/');
};
const navigateToLessonSelectPage = () => {
navigateToHomePage();
getModuleListItems().first().click();
};
Cypress.Commands.add('navigateToHomePage', navigateToHomePage);
Cypress.Commands.add('navigateToLessonSelectPage', navigateToLessonSelectPage);
export {};
We have modified the navigateToLessonSelectPage()
function to first call the navigateToHomePage()
function, and once we are on the home page we grab the list of module items (we have imported the getModuleListItems
function to help with this) and click the first one. This will get us to the lesson select page in a way that a real user would get there.
If we run the E2E tests again now, it still fails, but we get a different error:
expected <ion-item.item.md.item-fill-none.in-list.ion-activatable.ion-focusable.hydrated> to contain text lesson1, but the text was ''
Let's fix this test by adding in the title to the template now.
Modify the list in
src/app/lesson-select/lesson-select.page.html
to reflect the following:
<ion-header>
<ion-toolbar>
<ion-title>LessonSelect</ion-title>
</ion-toolbar>
</ion-header>
<ion-content>
<ion-list *ngIf="module$ | async as module">
<ion-item
data-test="lesson-list-item"
button
*ngFor="let lesson of module.lessons"
>
{{ lesson.title }}
</ion-item>
</ion-list>
</ion-content>
Now if we run the E2E tests:
expected <ion-item.item.md.in-list.ion-activatable.ion-focusable.hydrated> to contain text lesson1, but the text was test
We still get an error because, unlike with our unit tests, the data is being supplied by the real module service which looks like this:
public getModuleById(id: number) {
return of({
id: 1,
title: 'Module One',
description: 'Test',
lessons: [{ title: '1' }],
});
}
Let's modify that to include the type of lesson data we would expect.
Modify the
getModuleById
method insrc/app/services/modules.service.ts
to reflect the following:
public getModuleById(id: number) {
return of({
id: 0,
title: '',
description: '',
lessons: [
{ title: 'lesson1' },
{ title: 'lesson2' },
{ title: 'lesson3' },
{ title: 'lesson4' },
],
});
}
If we run the E2E tests once more:
Home
✓ should be able to view a list of modules
✓ the list of modules should contain the titles of the modules
✓ after selecting a specific module, the user should be able to see a list of available lessons
Lesson Select
✓ the list of lessons should contain the titles of the lessons
All of the tests will pass. One thing I'd like to point out at this stage is how little you need to rely on running ionic serve
to check your application when using a TDD approach. All of the little "Oops! I forgot to add this" moments are covered by the tests, and this becomes your method for iterating on the application rather than constantly manually inspecting your application in the browser.
Now let's get to work on implementing our main E2E test for this lesson.
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).