AngularTDD

Tutorial: Developing an angular app driven by tests

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 icons 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.

We specify an accordingly test, start it  and watch it fail .

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/contact-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.

   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'));
  }
}

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 app-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.

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[].

 This time we start the karma test server and runner with ng testand 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>

   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);
});

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>

   Success! Our component now displays the contact´s names.

Are we finished? We have a look at the e2e-tests.

 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");
});

 Now we also have a failing component test.

We initialize the controller´s contact-property with that contacts.

   All component tests 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

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert