This article is a tutorial for developing Angular applications driven by tests (TDD).
Introduction
For a long time graphical user interfaces of complex systems were made of solid and heavy weighted technologies of higher developed, object oriented programming languages.
Mostly UIs have been fat clients, developed with WPF or Swing. Just rarely they were thin clients as web applications.
And even in this seldom cases, HTML and JavaScript got abstracted by big frameworks like ASP or JSF and rendered on server side.
But the language core of JavaScript evolved well with ECMAScript 2015 and so got interesting for more complex applications, too.
Based on the suggestions to ECMAScript2015, TypeScript raised and became an additional option to build web applications on a very high language level that even types statically.
This big language improvements and the strong support of giants like Facebook and Google made web technologies to get more and more interesting for developing more complex applications as single page applications (SPA).
Unfortunately the source code of web applications still has the bad record to be on a lower quality level when regarding readability, design and test coverage.
But the value of creating high quality code, for sustainable further development and maintenance, is independent of the technology and so are the methodologies that ensures that value.
Test driven development (TDD) is a methodology of the Extreme Programming (XP) to develop high quality and well tested code.
In TDD we first specify and validate the behavior in tests, before we start to implement it. When the tests succeeds, we refactor the code to improve the readability and structure of the code.
This leads to well testable and tested code and a process where developers early have to think about the structure, respectively the design of the code.
By specifying functionality in tests first, we effectively only develop functionality that is required and preserves against developing predictive and unnecessary complex code.
Projects created with the Angular CLI, out of the box contains all tools we need to develop driven by tests.
This tutorial bases on a simple scenario and describes how to develop Angular project driven by tests.
The scenario
We want to focus the methodology and so choose a very simple scenario.
The application is a very lightweight contact management tool and provides only two functionalities:
- displaying contacts
- adding new contacts
Setup the Angular project
We first create an Angualr project with the Angular CLI.
ng new ContactManager
We want to start from the scratch, so we:
- delete the component app.component
- cleanup dependents app.module und index.html
- delete the end-to-end specifications in the directory e2e/src
- and check our changes by building the project with
ng build
The „Walking Skeleton“
Our system is very small, does not communicate with any backend and for the sake of convenience it is not deployed automatically.
So in our case the walking skeleton is a crosscut for developing our application driven by tests.
This should contain an e2e-test that interacts with the web application by page objects and a first small component which is developed driven by tests.
A suitable first small story to build the walking skeleton is to display the contacts.
Approach
We develop in short TDD iterations.
I am using some special text styles to get the tutorial more readable and clear:
First we talk about and specify what we want to achieve in the next tdd
iteration. In best case we don´t develop for our own, but have paired with
another developer.
Test fails! We specify an accordingly test, start it and watch it fail.
Test succeeds! After that we implement the functionality and start the test again until it succeeds.
We check our code style, make some refactorings and after that we start with the next small tdd iteration.
Specifying the story in a first e2e-test
We specify the context of the “contact management” in a new e2e-specification /e2e/src/c_ontact-management.e2e-spec.ts_.
We define, that the contact management initially lists the two contacts “Max” and “Moritz”.
Within the e2e-test we want to interact with the application on a very high level and delegate the access to the HTML-Elements to so called “page objects”.
In TDD manner, we write the e2e-test, as if there would already exist a class _ContactManagementPage _and so define it in the most meaningful way from the clients point of view.
We create an instance of the ContactManagementPage, navigate to its view and verify that the contacts “Max” and “Moritz” are listed.
describe('Contact Management', function () {
it('should show a list of contacts, initially containing "Max" and "Moritz"', function () {
const page: ContactManagementPage = new ContactManagementPage();
page.navigateTo();
expect(page.shownNamesOfContacts()).toContain('Max');
expect(page.shownNamesOfContacts()).toContain('Moritz');
});
});
When finished, we create the class ContactManagementPage /e2e/src/pageobjects/contact-management.po.ts, as assumed in the e2e-test.
Test fails! Now the test compiles, but fails as expected.
We start implementing the page object´s navigateTo-method, which navigates to the contact management view. In our case it is the root content “/”.
Next we need the names of all listed contacts. At this point, we define that the view contains HTML-elements of the class “contact”, which again contains elements of the class “name”.
We implement #shownNamesOfContacts by getting these elements and map their text values.
Protractor works asynchronous with its own Promise class. We have to make sure to import and use the right Promise-Class of the Protractor library.
import {browser, by, element, ElementArrayFinder, ElementFinder, promise} from 'protractor';
import Promise = promise.Promise;
export class ContactManagementPage {
navigateTo() {
browser.get('/');
}
shownNamesOfContacts(): Promise<string[]> {
return this.contactNameItems().map(contactNameItem => contactNameItem.getText());
}
private contactNameItems(): ElementArrayFinder {
return element.all(by.css('.contact .name'));
}
}
Test fails! The test fails with the hint that no Angular application could be found. Currently no angular component gets bootstrapped.
To fix that issue we next create an Angular component “contact-list” with the Angular CLI.
ng generate component contact-list
It is automatically assigned to the global a_pp-module_ and declared within it. So far that´s ok for this tutorial, but we also have to integrate the component in our index.html and configure our module to bootstrap the component.
Test fails! The e2e-test still fails, but this time as intended by not fulfilling the expectations of the test.
Implementing the component
First the component should display a list of items per contact.
We create a test case in the contact-list.component.spec which was generated by the Angular CLI. While writing it, we again assume all dependencies already exist.
it('should display an element for each contact', function () {
component.contacts = [new Contact('DummyContact'), new Contact('DummyContact2')];
fixture.detectChanges();
const contactElements: NodeList = fixture.nativeElement.querySelectorAll('.contact');
expect(contactElements.length).toBe(2);
});
The code does not compile. We need a data structure „Contact“ and a property „contacts“ in the component´s controller contact-list.components.ts which provides the contacts_._
We create the class Klasse /model/contact.ts
export class Contact {
constructor(public name: string) {
}
}
and extend the controller by the property contacts: Contact[]
.
Test fails! This time we start the karma test server and runner with ng test
and watch the written test failing, because we expect two contact elements that are currently no displayed.
We keep karma running and so get instant feedback about our changes and their impact on the behavior.
To display an item for each contact, we extend the view contact-list.component.html.
<div>
<ul>
<li *ngFor="let contact of contacts" class="contact"></li>
</ul>
</div>
Test succeeds! The test succeeds!
So far, now we want to display the contact´s names.
it('a contact element should display the contact´s name', function () {
const contact = new Contact('SomeName');
component.contacts = [contact];
fixture.detectChanges();
const nameElement: HTMLElement = fixture.nativeElement.querySelector('.contact .name');
expect(nameElement).not.toBeNull();
expect(nameElement.textContent).toEqual(contact.name);
});
Test fails! As expected the test fails.
We make the list to display the name.
<div>
<ul>
<li *ngFor="let contact of contacts" class="contact">
<span class="name">{{contact.name}}</span>
</li>
</ul>
</div>
Test succeeds! Our component now displays the contact´s names.
Are we finished? We have a look at the e2e-tests.
Test fails! They still fail. We expect “Max” and “Moritz” to be listed initially.
We want our component to initially contain the contacts “Max” and “Moritz”.
it('should initially display the contacts "Max" and "Moritz"', function () {
const nameElements: NodeList = fixture.nativeElement.querySelectorAll('.contact .name');
const names: string[] = Array.from(nameElements, nameElement => nameElement.textContent);
expect(names).toContain("Max");
expect(names).toContain("Moritz");
});
Test fails! Now we also have a failing component test.
We initialize the controller´s contact-property with that contacts.
Test succeeds! All component tests succeeds!
Test succeeds! Now the e2e-test is also green and the implementation of the story is done!
Contact Management
√ should show a list of contacts, initially containing “Max” and “Moritz”
Further development
Now our skeleton walks and we can continue with further development.
Possible next steps could be:
- Refactoring: Extraction and delegation of the contact-list-specific content to its own page object to keep the contact-page-object clean and to reuse it in other tests.
- Story: Creation of new contacts
- Creating a new contact by entering its name in a textfield followed by pressing the enter-button
- when a new contact is created, the textfield should be cleared
- Optimization: Managing the data in a Redux-store
Conclusion
Frameworks like Jasmine, Karma or Protractor support test driven development of web applications very well.
In case of Angular, projects created by the Angular CLI are already preconfigured with Jasmin, Karma and Protractor and out of the box can be developed driven by tests.
So TDD is also for web applications a very good methodology to create tested and well designed source code on a very high quality level.
In combination with other methodologies like pair programming and clean coding, nice synergy effect arise to develop and maintain complex applications in an effective and efficient way.
See also Test strategies when developing redux stores in angular apps driven by tests and Pushing an angular project to the aws cloud
The post Tutorial: Developing an angular app driven by tests appeared first on maimArt.
Top comments (6)
This is great, Martin! So many examples say you should write the tests first, but then demonstrate how the framework works by writing the tests second.
I’m getting better at deciding which questions to ask/which pieces to test, but I would love to find more material on this topic. E.g., if I am going to use a shared module for all of my @angular/material components, what would I test for that module?
Thanks!
We would import our shared MaterialModule in a TestBed with a declared TestComponent and verify that its template is able to render the Angular Material components that we expect the shared module to re-export.
Here is an example:
stackblitz.com/edit/ngx-module-tes...
Of course, you can do so.
Thank you very much!
If you use it in all your components, it must handle cross cutting concerns like logging, data management, api access or something like that.
Especially in that case i would develop that module driven by tests more then ever. So i would start with an integration test for that module. Depending on the size and structure of the module, i would maybe break it down in smaller unit/component tests. But anyhow, i would always drive the development by tests.
For my liking the only reason not to test a component or module is that it is really humble and has no own behavior, like the views when using the "MVP Passive View"-pattern. Then, i think it is sufficient to only test it by e2e or system tests.
I think angular itself has a good guideline for testing (angular.io/guide/testing). And for TDD itself i love books about it from e.g. Uncle Bob and Kent Beck
Greets
Your article is missing a few steps, I am not sure if you did that on purpose...and there are typos as well...but nonetheless it helped me...and I was able to figure out the missing steps..and the typos...Thank you :)
Very nice! Here my attempt to show test driven angular: youtube.com/watch?v=oevY4WE1lnw