Test Driven Development with Protractor/Jasmine (Legacy)
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.
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
DEPRECATEDModule Outline
- Resources PRO
- Lesson 1: Introduction PRO
- Lesson 2: Introduction to Test Driven Development PRO
- Lesson 3: Testing Concepts PRO
- Lesson 4: Jasmine, Karma, and Protractor 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 PRO
- Lesson 9: Test Development Cycle PRO
- Lesson 10: Getting Ready 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 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();
});
});
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).