Introduction
In this article, we will learn how we can create end to end tests (e2e) for our Angular application. We will be using an existing end-to-end testing framework, which is nothing but, Protractor. Protractor runs your tests against your application running in a real browser, in the same way your user may perform the actions. I will be using an existing application which is available in my GitHub profile. The same is available in the source code section. Please do share your feedback with me. I hope you will like this article. Let's start coding.
Source Code
The source code can be found here. Please clone this repository before you start.
Importance of End to End Testing
Some of you might have already started writing end to end testing for your application, and you may be knowing how important that is. Here in this section, I will list down the key benefits of end to end testing.
- End to end testing tests the complete flow or the action. For example, a complete login process can be treated as one end to end testing. Basically, it tests a specific functionality.
- It doesn't improve the code quality as done in Unit testing. Unit testing is a different topic, and end to end testing and unit testing are completely different.
- As I said earlier, end to end testing run tests on the browser, so it tests your application live, with some real data.
- You can easily find out, if any functionalities are broken because of your recent changes or implementation.
Basic Overview
If you have already cloned the application, you should be able to see a folder e2e in your application code, open that folder. You can see three files as below:
- tsconfig.e2e.json
- app.po.ts
- app.e2e-spec.ts
Here tsconfig.e2e.json is the configuration file, if you open that file, you can see that the file is been extended from tsconfig.json.
{
"extends": "../tsconfig.json",
"compilerOptions": {
"outDir": "../out-tsc/e2e",
"baseUrl": "./",
"module": "commonjs",
"target": "es5",
"types": [
"jasmine",
"jasminewd2",
"node"
]
}
}
app.po.ts is the page
object, this is really helpful and important. Here is the place, where we will write the code to find out the elements in our page or view. So, in future, if you are changing the selectors of your element, your changes will impact only in this file, so that you don't need to change anything in your tests. Isn't that handy? By default, this file will have the code as below.
import { browser, by, element } from 'protractor';
export class AppPage {
navigateTo() {
return browser.get('/');
}
getParagraphText() {
return element(by.css('app-root h1')).getText();
}
}
As you can see, on the first line, we are importing browser
, by
, element
from protractor.
browser
is for interacting with browser by
is for finding the element by CSS or any other function element
is for converting the selected element
Here in this line, element(by.css('app-root h1')).getText()
, like you have already guessed, we are just finding an element with the selector 'app-root h1
'.
app.e2e-spec.ts is the tests holder. We will be writing all of our tests here.
import { AppPage } from './app.po';
describe('my-angular5-app App', () => {
let page: AppPage;
beforeEach(() => {
page = new AppPage();
});
it('should display welcome message', () => {
page.navigateTo();
expect(page.getParagraphText()).toEqual('Welcome to ng5!');
});
});
The first thing we do is, importing the page, in this case AppPage
. This will be very easy for you if you have already written any unit test cases using jasmine. If you are not sure about how you can write the unit tests, I recommend you visit this link.
Once we import the page, we are declaring an object and initialize the same with AppPage
instance in beforeEach
function, so that the code can be executed before each tests runs.
And in our first test, we are just confirming that the title is 'Welcome to app
' by getting the value from our page
object by calling page.getParagraphText()
.
Creating Login Component
As you all know, a component will have two files by default:
- login.component.html
- login.component.ts
login.component.html
Let's write some HTML code for our component.
<div class="container" style="margin-top:100px;">
<div class="row justify-content-center align-items-center">
<div class="col-lg-4 col-sm-4 center-block ">
<mat-card>
<mat-card-header>
<img mat-card-avatar src="../../../assets/images/App-login-manager-icon.png">
<mat-card-title>Login here</mat-card-title>
<mat-card-subtitle>Trust us for your data, and sign up</mat-card-subtitle>
</mat-card-header>
<mat-card-content>
<div class="signup-fields">
<form id="loginForm"[formGroup]="form" (ngSubmit)="login()">
<div class="form-group">
<input name="email" class="form-control"
matInput type="email" placeholder="Email" formControlName="email" />
</div>
<div class="form-group">
<input name="password" class="form-control"
matInput type="password" placeholder="Password"
formControlName="password" />
</div>
<div>
<button id="btnSubmit" mat-raised-button type="submit"
color="primary">Login</button>
</div>
</form>
</div>
</mat-card-content>
</mat-card>
</div>
</div>
</div>
login.component.ts
import { Component, OnInit } from '@angular/core';
import { FormBuilder, Validators } from '@angular/forms';
import { Router } from '@angular/router';
import { AuthService } from '../auth.service';
@Component({
selector: 'app-login',
templateUrl: './login.component.html',
styleUrls: ['./login.component.css']
})
export class LoginComponent implements OnInit {
form;
constructor(private fb: FormBuilder,
private myRoute: Router,
private auth: AuthService) {
this.form = fb.group({
email: ['', [Validators.required, Validators.email]],
password: ['', Validators.required]
});
}
ngOnInit() {
}
login() {
if (this.form.valid) {
this.auth.sendToken(this.form.value.email)
this.myRoute.navigate(["home"]);
}
}
}
If you run the application, you can see that we are accepting the form only if we give relevant values to the fields, if not given, the form will be invalid. In our next step, we will write end to end test for this functionality. Sounds good?
Write End to End Tests for Login Component
To get started, let us create two files as below:
- login.po.ts
- login.e2e-spec.ts
Now let us define our page and some functions in login.po.ts.
Set up login.po.ts
Open the file and write some code as preceding.
import { browser, by, element } from 'protractor';
export class LoginPage {
navigateTo(){
return browser.get('/login');
}
}
Now we will write the code to find the email and password text boxes.
getEmailTextbox() {
return element(by.name('email'));
}
getPasswordTextbox() {
return element(by.name('password'));
}
Set up login.e2e-spec.ts
It is time to set up our spec file before we start writing the tests.
import { LoginPage } from './login.po';
describe('Login tests', () => {
let page: LoginPage;
beforeEach(() => {
page = new LoginPage();
page.navigateTo();
});
});
We have imported our LoginPage
and initialized the same. It is time to write the tests now.
it('Login form should be valid', () => {
page.getEmailTextbox().sendKeys('info@sibeeshpassion.com');
page.getPasswordTextbox().sendKeys('1234');
let form = page.getForm().getAttribute('class');
expect(form).toContain('ng-valid');
});
Here what we are doing is, set the values to our text boxes by using sendKeys
function and then find the class
attribute of our form, so that we can check whether it is valid or not. If the form is valid, the form will be having the class
as ng-valid
, if not, it will have ng-invalid
class.
Run End to End Test
Running an end to end test is as easy as falling off a log. As we are using Angular CLI, all we have to do is run the command ng e2e
. This will be set in our package.json file.
"scripts": {
"ng": "ng",
"start": "ng serve",
"build": "ng build",
"test": "ng test",
"lint": "ng lint",
"e2e": "ng e2e"
}
Let us run ng e2e
now.
PS F:\My Projects\ng5> ng e2e
If everything goes well, your application should be opened in a browser and test the functionality. You will also get a message saying "Chrome is being controlled by automated test software".
ng e2e browser output
And in your terminal console, you should see an output saying all of your tests are passed.
Automated test output
Writing Few More Tests
Let us write few more tests to check some other functionalities.
Check Form Is Invalid
To check the form is invalid or not, we need to pass some invalid data to the form. The final test should be as follows:
it('Login form should be invalid', () => {
page.getEmailTextbox().sendKeys('');
page.getPasswordTextbox().sendKeys('');
let form = page.getForm().getAttribute('class');
expect(form).toContain('ng-invalid');
});
Check Whether the Value Is Been Saved to Local Storage
You might have already looked at what is the functionality we are doing when we click the Login button in our application. For now, we are just saving the email value to the local storage. Below is the function which gets called when we click the Login button.
login() {
if (this.form.valid) {
this.auth.sendToken(this.form.value.email)
this.myRoute.navigate(["home"]);
}
}
And this is the sendToken
method in our AuthService
.
sendToken(token: string) {
localStorage.setItem("LoggedInUser", token)
}
Now, we are going to write automated test for this functionality. First, let us add a function which returns Submit button in login.po.ts.
getSubmitButton() {
return element(by.css('#btnSubmit'));
}
Now, write the test as follows:
it('Should set email value to local storage', () => {
page.getEmailTextbox().sendKeys('info@sibeeshpassion.com');
page.getPasswordTextbox().sendKeys('1234');
page.getSubmitButton().click();
let valLocalStorage = browser.executeScript
("return window.localStorage.getItem('LoggedInUser');");
expect(valLocalStorage).toEqual('info@sibeeshpassion.com');
});
As you can see that, we are actually setting some valid data to the form and triggering the click event of our button, and the value of the email text box is being saved to the localStorage. In our test, we are checking the value of the localStorage
by executing the script browser.executeScript("return window.localStorage.getItem('LoggedInUser');")
. We should get an output as below if everything is fine.
locaStorage in Protractor
Conclusion
Thanks a lot for reading. I will come back with another post on the same topic very soon. Did I miss anything that you think is needed? Could you find this post useful? I hope you liked this article. Please share me your valuable suggestions and feedback.
Your Turn. What Do You Think?
A blog isn’t a blog without comments, but do try to stay on topic. If you have a question unrelated to this post, you’re better off posting it on C# Corner, Code Project, Stack Overflow, ASP.NET Forum instead of commenting here. Please post your question in the comments section below and I’ll definitely try to help if I can.
History
- 18th July, 2018: Initial version