Testing Integration with a Server
Using E2E tests to test against the real server
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
Testing Integration with a Server and Using Mock Backend
With the server set up, we will now be able to start implementing the functionality for this requirement:
- A user should only be able to log in to the application with a valid license key from SendOwl
As always, we will start with the E2E test and work from there. As you will soon see, this E2E test is going to take us down a rather long road.
Create the E2E Test
In order to implement this functionality, we are clearly going to need to implement some sort of login page that will be displayed before the rest of the application. Since we will be adding a new page, we are going to create a new spec file for our E2E tests as well as a new navigation command and helper functions.
Modify
cypress/support/commands.ts
to include the following function:
const navigateToLoginPage = () => {
cy.visit('/');
};
IMPORTANT: Remember to add the Cypress command at the bottom of the file, and add the new function to the Cypress
namespace as well.
Add the following helper functions to
cypress/support/utils.ts
:
export const getKeyInput = () => cy.get('[data-test="key-input"] input');
export const getLoginButton = () => cy.get('[data-test="login-button"]');
Now we will be able to grab a reference to a key-input
field that will be used to enter the key, and a login button that will be used to trigger sending the key to the server.
If you've been paying especially close attention, you may notice that the navigateToLoginPage
function is the same as what we are currently using for the home page. The login page will eventually become the new default page, which means the navigateToHomePage
function for the home page will no longer work and it's going to break all of our tests - we'll deal with that later.
Now let's create the E2E test for the login page.
Create a file at
cypress/e2e/login.cy.ts
and add the following:
import {
getKeyInput,
getLoginButton,
getModuleListItems,
} from '../support/utils';
describe('Login', () => {
beforeEach(() => {
cy.navigateToLoginPage();
});
it('a user should be able to reach the home page by providing a valid license key', () => {
getKeyInput().type('abcd-egfh-ijkl-mnop');
getLoginButton().click();
getModuleListItems().first().should('contain.text', 'Module One');
});
});
We've created a test that will first send a valid key to the input field (remember, with the server we set up any value will be treated as a valid key) and then the login button will be clicked. This is the first time we have seen type
which allows us to send text input to an element, but as are most things with Cypress it is quite intuitive to understand.
Let's run that test now and see what kind of errors we get.
Run the E2E tests
You should get the following result:
Expected to find element: [data-test="key-input"] input, but never found it.
We haven't implemented our login page yet so our <input>
field for entering the key doesn't exist. Let's work on that now.
Run the following command to generate the
LoginPage
:
ionic g page Login
Modify the routes in
src/app/app-routing.module.ts
to reflect the following:
import { NgModule } from '@angular/core';
import { PreloadAllModules, RouterModule, Routes } from '@angular/router';
const routes: Routes = [
{
path: 'home',
loadChildren: () =>
import('./home/home.module').then((m) => m.HomePageModule),
},
{
path: '',
redirectTo: 'login',
pathMatch: 'full',
},
{
path: 'module/:id',
loadChildren: () =>
import('./lesson-select/lesson-select.module').then(
(m) => m.LessonSelectPageModule
),
},
{
path: 'module/:moduleId/lesson/:lessonId',
loadChildren: () =>
import('./lesson/lesson.module').then((m) => m.LessonPageModule),
},
{
path: 'login',
loadChildren: () =>
import('./login/login.module').then((m) => m.LoginPageModule),
},
];
@NgModule({
imports: [
RouterModule.forRoot(routes, { preloadingStrategy: PreloadAllModules }),
],
exports: [RouterModule],
})
export class AppRoutingModule {}
Notice that we have changed the redirectTo
to login
instead of home
since we want the login page to be our default page now.
Modify
src/app/login/login.page.spec.ts
to reflect the following:
import {
ComponentFixture,
TestBed,
waitForAsync,
fakeAsync,
tick,
} from '@angular/core/testing';
import { IonicModule, NavController } from '@ionic/angular';
import { LoginPage } from './login.page';
describe('LoginPage', () => {
let component: LoginPage;
let fixture: ComponentFixture<LoginPage>;
beforeEach(waitForAsync(() => {
TestBed.configureTestingModule({
declarations: [LoginPage],
providers: [
{
provide: NavController,
useValue: {
navigateRoot: jest.fn(),
},
},
],
imports: [IonicModule.forRoot()],
}).compileComponents();
fixture = TestBed.createComponent(LoginPage);
component = fixture.componentInstance;
fixture.detectChanges();
}));
it('should create', () => {
expect(component).toBeTruthy();
});
});
Now if we run our E2E tests again with npm run e2e
. We should get the following if we run all the tests...
Isn't it kind of fun to watch everything fail catastrophically? Our tests are failing completely now. Since we are stuck on the login page (due to our recent routing change) all of our existing tests fail. Although this example is somewhat in your face, this is what tests are for. We can change code and develop new features with extra confidence that if something goes wrong our tests will (hopefully) fail and let us know about it. If we did somehow manage to replace the default route with a blank page that is impossible to navigate away from without realising it, we have our tests to back us up.
We need to start implementing the unit tests for the Login page so that we can create the functionality necessary to actually log in. We will also be creating a separate service to handle the requests to the server. Once we've done that, we can start fixing up all of our tests - don't worry, we'll be seeing green again in no time.
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).