Lesson 17

Testing Integration with a Server

WARNING: This module is deprecated and no longer receives updates. Protractor is likely being removed as the default from Angular applications and Protractor itself will likely stop receiving updates and development in the future. I would recommend checking out the Test Driven Development with Cypress/Jest as a replacement.

Using E2E tests to test against the real server

DEPRECATED

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 page object.

Create a file at e2e/src/login.po.ts and add the following:

import { browser, by, element, ElementFinder } from 'protractor';

export class LoginPageObject {
  navigateTo() {
    return browser.get('/');
  }

  getKeyInput() {
    return element(by.css('.key-input input'));
  }

  getLoginButton() {
    return element(by.css('.login-button'));
  }
}

This page object will allow us 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 navigateTo 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 navigateTo 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 e2e/src/login.e2e-spec.ts and add the following:

import { browser, protractor } from 'protractor';
import { HomePageObject } from './home.po';
import { LoginPageObject } from './login.po';

describe('Login', () => {
  let homePage: HomePageObject;
  let loginPage: LoginPageObject;

  beforeEach(async () => {
    homePage = new HomePageObject();
    loginPage = new LoginPageObject();
    await loginPage.navigateTo();
  });

  it('a user should be able to reach the home page by providing a valid license key', async () => {
    const input = loginPage.getKeyInput();
    const loginButton = loginPage.getLoginButton();

    input.sendKeys('abcd-egfh-ijkl-mnop');

    await loginButton.click();

    await browser.wait(protractor.ExpectedConditions.urlContains('home'));

    expect(await homePage.getModuleListItems().first().getText()).toContain(
      '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. We are using the ExpectedConditions functionality of protractor to wait on the condition that the URL contains home. This will indicate that the server has finished responding and we have redirected the user to the home page. It's important to have this wait condition because the server could potentially take a few seconds to respond.

As an alternative, you could also just use the sleep method, i.e:

await browser.driver.sleep(3000);

Although this is simpler, it is not as reliable as setting up an appropriate ExpectedConditions. The operation could finish faster than 3 seconds, in which case your test will run unnecessarily slow, or it could finish slower than 3 seconds in which case your test will break.

Let's run that test now and see what kind of errors we get.

Run the following command

npm run e2e

After running that, you should see the following:

1) Login a user should be able to reach the home page by providing a valid license key
  - Failed: No element found using locator: By(css selector, .login-button)

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 } from '@ionic/angular';

import { NavController } from '@ionic/angular';
import { NavMock } from '../../../mocks/mocks-ionic';

import { LoginPage } from './login.page';

describe('LoginPage', () => {
  let component: LoginPage;
  let fixture: ComponentFixture<LoginPage>;

  beforeEach(
    waitForAsync(() => {
      TestBed.configureTestingModule({
        declarations: [LoginPage],
        providers: [{ provide: NavController, useClass: NavMock }],
        imports: [IonicModule.forRoot()],
      }).compileComponents();

      fixture = TestBed.createComponent(LoginPage);
      component = fixture.componentInstance;
      fixture.detectChanges();
    })
  );

  it('should create', () => {
    expect(component).toBeTruthy();
  });
});
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).