Getting Started with Angular 2 End To End Testing
October 12th, 2016 | By Lamin Sanneh | 10 min read
Introduction to Angular 2 End-To-End Testing: there have been many reasons I have held out on adding automated testing to my applications in the past. One of them was not knowing the benefit vs. cost ratio. Another is the thought that they would be hard to integrate into existing production applications.
How do we test our apps without rewriting them from scratch to introduce testing into them?
Let’s start by briefly identifying the types of tests out there.
There are many types of application tests. However, the two most common are unit tests and end-to-end tests (also known as integration tests).
Unit testing
Unit testing is a type of testing that tests the behavior of your code itself. It has nothing to do with what the user sees but ensures your methods do what they must do.
Integration testing
It is a testing type that mimics what the user intended for the applications to do. So, it automates the logging process into a system, creating posts, and logging out. This all happens automatically, and you can see it happen visually.
Unit testing and Integration testing
These two types of tests are usually used in conjunction with each other.
For new applications, that would be ideal. When time is limited, or you inherited an existing application, end-to-end testing may be more suitable than unit tests: we do not have to rely on deep knowledge of the codebase at first. We can also cover more scenarios quicker than unit tests as they do not test single units but scenarios.
Unit tests are still necessary, but if you had to choose one to start, end-to-end testing is a better choice.
In this article, we will be testing an existing Angular 2 to-do application. Hence, we will be using integration tests and will cover several scenarios.
Scenarios to be tested
When the application initially loads, we should have three to-dos on the list.
We should be able to add a new to-do
We should be able to click on a to-do and be taken to the details page of that to-do.
We should be able to delete a to-do.
We should be able to edit a to-do title and see the new title reflected on the homepage in the list of to-dos, after saving it.
We shouldn’t be allowed to save an empty todo and the list of todos should still be the same length after clicking on the disabled save button
Initially, the add new to-do button should be disabled when we load the homepage.
The save to-do button should only be enabled when we start typing a to-do title.
To-do Application Outline
Let’s describe our to-do application briefly. The application will initially list out a list of to-dos on the homepage. Three, to be precise. The data will not come from a server but will be loaded from a hardcoded fixture file.
On the homepage, we can add new to-dos. We can also visit a to-do details page by clicking on its title. On this page, we can edit the to-do title or delete the to-do.
Clone and set the To-do Application
Clone the non-tested application I have pushed to the repository.
Make sure you are on the master branch. Next, you need to have several tools installed to be able to follow along. As of this tutorial, Angular 2 has come out of Release Candidate and is at version 2.Make sure you have NodeJS version Node 4.x.x or above installed.
Install the node dependencies using the command
npm install
while inside the cloned repository
Development is done using Angular-CLI.
Install the Angular-CLI globally using
npm install - g angular - cli@ latest
When all dependencies are installed, start the development server using
ng serve
and navigate to the browser URL http://localhost:4200 where you shall see three todos listed.
If you are having an issue starting the server, you may like to reference this StackOverflow issue to fix the issue.
Important Angular 2 Testing Concepts
End-to-end tests are located in the folder e2e. There is already a sample test file there called es2/app.e2e-spec.ts.
The tests in there are written using the Jasmine framework. There are many ways to modularize and organize Angular 2 end-to-end tests but for simplicity’s sake, we will put all the tests for this article in this one file.
Our application only has one concern which is todos. For curiosity’s sake and for those who want something more complex than the above, let’s imagine a scenario where this was a more complex application that has other concerns like orders, and userProfile. A way I would handle tests for that application is to create a folder inside of e2e for each of those concerns and put the respective tests in each folder.
In this case, we will have two folders named e2e/orders and e2e/userProfile. We can have just one test file in each folder or more than one to cater to subsections of each of those concerns if we need to. The only thing to be mindful of is that each of the files needs to end with the word e2e-spec.ts so that the protractor test runner can pick up the test files.
So, back to our simple, one-file test. If you look in the file, you will see an import statement at the top. That import is where we put common functions which will be used by several tests. In this article, however, we won’t be using it. Think of it as a library of functions.
After the import statement, we have a describe block that has two other function calls namely beforeEach and it inside its callback.
The callback passed to beforeEach is called for every test inside the describe block. Leave it as it is.
Individual tests are put inside of the callback passed to the it function.
Let’s run the current test using the command
protractor
If you have an issue running Protractor, run either one of these two commands:
. / node_modules / protractor / bin / webdriver - manager update
or
webdriver - manager update
The current test should fail as it expects to see the text “app works” on the homepage. This is not the case as we have modified the contents of the homepage.
Before we start writing our tests, let’s understand some of the most common functions we will be using in writing Angular 2 end-to-end tests.
Navigating to pages
Inside the test files, there is a global variable called browser available. It is imported using the import statement
import {
browser, element, by
}
from 'protractor/globals';
which you can add there now. We use this to navigate to any page available in our application by writing for example
browser.get('/');
to go to the homepage and
browser.get('/users');
to go to the user's page. Note that these URLs are relative but we can use absolute URLs as well I recommend using relative URLs as they are more maintainable in case your root URL changes.
Selecting elements
It is common practice to select elements on the current page. There is a global variable called element which you can use to select elements. It accepts a locator which can be created using the global called by
An example of selecting the p tag with the class of green is done using
let greenParagraph = element(by.css('p.green'));
To select many elements you should use the slight variation
let greenParagraphs = element.all(by.css('p.green'));
This will give an array as opposed to a single element.
Grabbing Element Text
To get the text of an element, you have to select it first as above and then call the getText function on the result like so.
let greenParagraph = element(by.css('p.green'));
let text = greenParagraph.getText();
Clicking Elements
Clicking elements can be done using the syntax below
let submitButton = element(by("form .submit-button"));
submitButton.click();
Counting Elements
We can also count elements using the syntax below.
let blueParagraphsList = elements.all(by("p.blue"));
let count = blueParagraphsList.count();
Test Scenarios
With the concepts out of the way, let’s now cover the scenarios we listed above.
Confirm that we have three Todos initially
When the application initially loads, we should have three todos in the list.
Inside the test file e2e/app.e2e-spec.ts, delete the call to it function below the beforeEach block and add there
it("should show three todos when we first load the todo app", () => {
browser.get("/");
let todos = element.all(by.css(".todos .todo"));
expect(todos.count()).toEqual(3);
})
Don’t forget to add this import statement at the top of this file
import {
browser, element, by
}
from 'protractor/globals';
Now when you run the protractor command, another browser window will open up and close quickly and in your console, you should see a passing test in green.
Hurray! We have just written our first passing Angular 2 end-to-end test.
Add a new To-do
Now onto the next one. We should be able to add a new todo. Let’s add another test block using the following code
it("should be able to add a new todo", () => {
browser.get("/");
let newTodoInput = element(
by.css(".add-todo input[type=text]"));
newTodoInput.sendKeys("Todo 4");
let newTodoSubmitButton = element(
by.css(".add-todo input[type=submit]"));
newTodoSubmitButton.click();
let todos = element.all(by.css(".todos .todo"));
expect(todos.count()).toEqual(4);
})
What we’ve done here is enter text into the new todo input box and then submit the form. Then we checked if we now have four todos. If that is the case, then our test has passed.
We just introduced another function called sendKeys. This can be called on a selected element. It is used to enter text into elements like input fields.
View the To-do Details page
We should be able to click on a todo and go to the details page of that todo. Let’s do that using the following test.
it("should be able to click on a todo on the homepage and get to
the details page", () => {
browser.get("/");
let firstTodo = element.all(by.css(".todos .todo")).first();
let firstTodoText = firstTodo.getText();
firstTodo.click();
let inputFieldText = element(
by.css("todo input[type=text]")).getAttribute("value");
expect(inputFieldText).toEqual(firstTodoText);
})
Delete a To-do
We should be able to delete a to-do. Now let’s try to delete a todo and see if we are successful.
We will go to the todo page and click on the delete todo link. When we return to the homepage, we should have one less to-do.
it("should be able to delete a todo", () => {
browser.get("/");
let firstTodo = element.all(by.css(".todos .todo")).first();
firstTodo.click();
let deleteLink = element(by.cssContainingText("span", "Delete"));
deleteLink.click();
let todosList = element.all(by.css(".todos .todo"));
expect(todosList.count()).toEqual(2);
})
Edit a Todo title
We should be able to edit a todo title and see the new title reflect on the homepage in the list of todos, after saving the todo
it("should be able to edit a todo title", () => {
browser.get("/");
let firstTodo = element.all(by.css(".todos .todo")).first();
firstTodo.click();
let todoInputField = element(by.css("todo input[type=text]"));
todoInputField.clear();
todoInputField.sendKeys("Editted Todo1 Title");
let saveButton = element(by.css("todo button[type=submit]"));
saveButton.click();
firstTodo = element.all(by.css(".todos .todo")).first();
let firstTodoText = firstTodo.getText();
expect(firstTodoText).toEqual("Editted Todo1 Title");
})
Cannot save empty To-do
When we want to save an empty todo, we shouldn’t be allowed to and the list of todos should still be the same length after clicking on the disabled button.
it("should not be able to save an empty todo", () => {
browser.get("/");
let newTodoInput = element(by.css(".add-todo input[type=text]"));
let newTodoSubmitButton = element(
by.css(".add-todo input[type=submit]"));
newTodoSubmitButton.click();
let todos = element.all(by.css(".todos .todo"));
expect(todos.count()).toEqual(3);
})
The save To-do button is disabled initially
Initially, the add new todo button should be disabled so add these lines of code
it("should have add todo button be disabled initially", () => {
browser.get("/");
let newTodoSubmitButton = element(
by.css(".add-todo input[type=submit]"));
expect(newTodoSubmitButton.isEnabled()).toEqual(false);
})
Enable the save Todo button when we start typing
The save todo button should only be enabled only when we start typing a todo title.
it("should only enable save todo button when we start typing
a new todo title", () => {
browser.get("/");
let newTodoSubmitButton = element(
by.css(".add-todo input[type=submit]"));
let newTodoInputField = element(by.css(".add-todo input[type=text]"));
newTodoInputField.sendKeys("New Todo 4");
expect(newTodoSubmitButton.isEnabled()).toEqual(true);
})
Conclusion
That brings us to the end of this Angular 2 end-to-end testing article.
End-to-end tests can be written quickly without any knowledge of the codebase. It is a quick way of making sure that any new changes you make to the codebase, which could potentially introduce bugs, will be caught quickly.
We did introduce other additional methods to the ones we talked about in the concepts section. You can find the completed and tested version of this application at this github repository.
I hope this was an encouraging introduction to start testing your front-end applications even before you change a single line of the project’s code.
That’s it for now and please let us know your thoughts and experiences about testing in general or just about Javascript frameworks or Angular 2 if you will.
Jscrambler
The leader in client-side Web security. With Jscrambler, JavaScript applications become self-defensive and capable of detecting and blocking client-side attacks like Magecart.
View All ArticlesMust read next
Getting Started with Angular 2
Angular 2 has been built to be completely decoupled from the DOM, meaning that developers can use the framework to build more than just web applications.
November 10, 2016 | By Thomas Greco | 6 min read
Angular 2 and Typescript Conference Browser Application
How to build a conference browsing application using typescript and the Angular 2 CLI
September 6, 2016 | By Jscrambler | 11 min read