Angular series: Creating a Login with TDD 
Tags: angular, tdd, javascript, tutorial
Published: 2019-11-18 14:55:16

---
title: Angular series: Creating a Login with TDD 
published: true
description: How to create a login component in angular with TDD
tags: angular, TDD, javascript, tutorial
series: angular
---

Let's create a Login page with Angular and TDD. The final project could be found in my personal Github: [Angular series](https://github.com/JPBlancoDB/angular-series)

### First step: Creating the project

Let's start by creating a new angular project:

```
ng new [project-name]
```

In my case, I created `ng new angular-series` and then select with routing and your file style extension of preference. 

![Screenshot of the CLI](https://thepracticaldev.s3.amazonaws.com/i/vqihnpfmftolqafodcy9.jpg)

An equivalent alternative would be just adding the respective options:

```bash
ng new angular-series --style=css --routing
```

More options of the CLI could be found in the official docs: [ng new](https://angular.io/cli/new)

Now, if we run `npm start` we should everything working, and `npm run test` we should also see 3 tests passing.

### Second step: App Component

Our goal is going to show our login page, so let's modify the current tests to reflect our intention:

We should remove the tests from `src/app/app.component.spec.ts` that no longer make sense:

```javascript
it(`should have as title 'angular-series'`, () => {
    const fixture = TestBed.createComponent(AppComponent);
    const app = fixture.debugElement.componentInstance;

    expect(app.title).toEqual('angular-series');
});

it('should render title', () => {
    const fixture = TestBed.createComponent(AppComponent);
    fixture.detectChanges();
    const compiled = fixture.debugElement.nativeElement;

    expect(compiled.querySelector('.content span').textContent)
      .toContain('angular-series app is running!');
});

```

And replace it with:

```javascript
it('should have router-outlet', () => {
    const fixture = TestBed.createComponent(AppComponent);

    expect(fixture.nativeElement.querySelector('router-outlet')).not.toBeNull();
});
```

This way we expect that our `app.component` has `<router-outlet></router-outlet>` defined, and this is needed for the router to inject other components there. More information: [Router Outlet](https://angular.io/guide/router#router-outlet)

If you noticed, our test is already passing. This is because the default `app.component.html` already has that directive. But now, we are going to remove the unnecessary files. Remove `app.component.html` and `app.component.css`. Check your console, you should see an error because `app.component.ts` is referencing to those files we've just removed.

Let's first fix the compilation errors:

```javascript
//app.component.ts
import { Component } from '@angular/core';

@Component({
  selector: 'app-root',
  template: 'hello world'
})
export class AppComponent {}

```

*Notice the difference between `templateUrl: ...` and `template`*

If we open `http://localhost:4200` we should see: "hello world", but now our test is failing (is important to first check that our test is failing and then make it "green", read more about the *Red, Green, Refactor* here: [The cycles of TDD](https://blog.cleancoder.com/uncle-bob/2014/12/17/TheCyclesOfTDD.html)) 

Ok, now that we have our failing test, let's fix it:


```javascript
//app.component.ts
import { Component } from '@angular/core';

@Component({
  selector: 'app-root',
  template: '<router-outlet></router-outlet>'
})
export class AppComponent {}

```

### Third step: Creating the Login Component

Open the terminal and run:

```bash
ng generate module login --routing
```

You should see:
- src/app/login/login.module.ts
- src/app/login/login-routing.module.ts

Next, create the login component:

```bash
ng generate component login
```

You should see:
- src/app/login/login.component.css
- src/app/login/login.component.html
- src/app/login/login.component.spec.ts
- src/app/login/login.component.ts

Finally, let's reference our newly created module into our `app-routing.module.ts`

```javascript
const routes: Routes = [
  {
    path: '',
    loadChildren: () => import('./login/login.module').then(m => m.LoginModule),
    data: { preload: true }
  }
];
```

End result:

```javascript
//app-routing.module.ts
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';

const routes: Routes = [
  {
    path: '',
    loadChildren: () => import('./login/login.module').then(m => m.LoginModule),
    data: { preload: true }
  }
];

@NgModule({
  imports: [RouterModule.forRoot(routes)],
  exports: [RouterModule]
})
export class AppRoutingModule {}

```

And we should also modify our `login-routing.module.ts`:

```javascript
//login-routing.module.ts
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { LoginComponent } from './login.component';

const routes: Routes = [
  {
    path: '',
    component: LoginComponent
  }
];

@NgModule({
  imports: [RouterModule.forChild(routes)],
  exports: [RouterModule]
})
export class LoginRoutingModule {}

```

If you open `http://localhost:4200`, you should see: "login works!"

### Fourth step: Login component

*Before we start, we could remove the unnecessary css file.*

First, let's create our test that asserts we have a form rendered:

```javascript
//login.component.spec.ts
  it('should render form with email and password inputs', () => {
    const element = fixture.nativeElement;

    expect(element.querySelector('form')).toBeTruthy();
    expect(element.querySelector('#email')).toBeTruthy();
    expect(element.querySelector('#password')).toBeTruthy();
    expect(element.querySelector('button')).toBeTruthy();
  });
```

We should have our failing test :sunglasses:. Now, we need to make it pass!

Let's do that, open `login.component.html`:

```html
<form>
  <input id="email" type="email" placeholder="Your email" />
  <input id="password" type="password" placeholder="********" />
  <button type="submit">Sign in</button>
</form>
```

We should see that we have 4 passing tests! Great, but still we don't have a usable form.

So, let's add a test for our form model (we're going to use [Reactive forms](https://angular.io/guide/reactive-forms))

```javascript
//login.component.spec.ts

  it('should return model invalid when form is empty', () => {
    expect(component.form.valid).toBeFalsy();
  });
```

As you could notice an error is thrown `error TS2339: Property 'form' does not exist on type 'LoginComponent'. `. 

Let's define our `form` in our `login.component.ts`:

```javascript
import { Component, OnInit } from '@angular/core';
import { FormGroup } from '@angular/forms';

@Component({
  selector: 'app-login',
  templateUrl: './login.component.html'
})
export class LoginComponent implements OnInit {
  form: FormGroup;

  constructor() {}

  ngOnInit() {}
}
```

We see that the compilation error is not there anymore, but we still have our test failing.

Why you think is still failing if we already declared `form`? 
That's right! Is still undefined! So, in the `ngOnInit` function let's initialize our form using `FormBuilder`:

```javascript
import { Component, OnInit } from '@angular/core';
import { FormGroup, FormBuilder } from '@angular/forms';

@Component({
  selector: 'app-login',
  templateUrl: './login.component.html'
})
export class LoginComponent implements OnInit {
  form: FormGroup;

  constructor(private formBuilder: FormBuilder) {}

  ngOnInit() {
    this.form = this.formBuilder.group({});
  }
}
```

Oh no! Now, we have more than 1 test failing!!! Everything is broken! Don't panic :wink:, this is because we have added a dependency to `FormBuilder` and our testing module does not know how to solve that. Let's fix it by importing `ReactiveFormsModule`:

```javascript
//login.component.spec.ts

  beforeEach(async(() => {
    TestBed.configureTestingModule({
      declarations: [LoginComponent],
      imports: [ReactiveFormsModule] //here we add the needed import
    }).compileComponents();
  }));

```

But, we still have 2 tests failing! We need to add `formGroup` to our `<form>`:

```html
<form [formGroup]="form">
```

Now, we should only see failing our `form is invalid` test :smiley:.

How do you think we could make our form invalid to make the test pass? 
Yes, adding our form controls with required validators. So, let's add another test to assert it:

```javascript
//login.component.spec.ts
it('should validate email input as required', () => {
  const email = component.form.controls.email;

  expect(email.valid).toBeFalsy();
  expect(email.errors.required).toBeTruthy();
});
```

Let's make those tests pass:

```javascript
ngOnInit() {
  this.form = this.formBuilder.group({
    email: ['', Validators.required]
  });
}
```

Great :sunglasses:! We need also a password property in our form with the required validator.


```javascript 
//login.component.spec.ts
it('should validate password input as required', () => {
  const password = component.form.controls.password;

  expect(password.valid).toBeFalsy();
  expect(password.errors.required).toBeTruthy();
});
```

To make it green we need to add password property to our form declaration:

```javascript
ngOnInit() {
  this.form = this.formBuilder.group({
    email: ['', Validators.required],
    password: ['', Validators.required]
  });
}
```

Let's verify that we should insert a valid email:

```javascript
it('should validate email format', () => {
  const email = component.form.controls.email;
  email.setValue('test');
  const errors = email.errors;

  expect(errors.required).toBeFalsy();
  expect(errors.pattern).toBeTruthy();
  expect(email.valid).toBeFalsy();
});
```

For adding the correct validator, we need to add a regex pattern like this:

```javascript
ngOnInit() {
  this.form = this.formBuilder.group({
    email: ['', [Validators.required, Validators.pattern('[^ @]*@[^ @]*')]],
    password: ['', Validators.required]
  });
}
```

We could add an extra test to validate that is working as expected:

```javascript
it('should validate email format correctly', () => {
  const email = component.form.controls.email;
  email.setValue('test@test.com');
  const errors = email.errors || {};

  expect(email.valid).toBeTruthy();
  expect(errors.required).toBeFalsy();
  expect(errors.pattern).toBeFalsy();
});
```

It is time for rendering errors in our HTML. As we are getting used to, we need first to add a test.

```javascript
it('should render email validation message when formControl is submitted and invalid', () => {
  const elements: HTMLElement = fixture.nativeElement;
  expect(elements.querySelector('#email-error')).toBeFalsy();

  component.onSubmit();

  fixture.detectChanges();
  expect(elements.querySelector('#email-error')).toBeTruthy();
  expect(elements.querySelector('#email-error').textContent).toContain(
    'Please enter a valid email.'
  );
});
```

Of course, as we didn't define an `onSubmit` function it is failing. Add `onSubmit() {}` in our `login.component.ts` and there it is, our beautiful red test :smiley:.

How to make this test green? We need a submitted property as stated in our test for only showing errors after we trigger the onSubmit:

```javascript
//login.component.ts

import { Component, OnInit } from '@angular/core';
import { FormGroup, FormBuilder, Validators } from '@angular/forms';

@Component({
  selector: 'app-login',
  templateUrl: './login.component.html'
})
export class LoginComponent implements OnInit {
  form: FormGroup;
  submitted = false;

  constructor(private formBuilder: FormBuilder) {}

  ngOnInit() {
    this.form = this.formBuilder.group({
      email: ['', [Validators.required, Validators.pattern('[^ @]*@[^ @]*')]],
      password: ['', Validators.required]
    });
  }

  onSubmit() {
    this.submitted = true;
  }
}

```

And add the validation message error in the HTML

```html
<span *ngIf="submitted && form.controls.email.invalid" id="email-error">
  Please enter a valid email.
</span>
```

Good, now we have our test green but if we run our app we are not going to see the error message after clicking `Sign in`. 

What is wrong? YES, our test is calling `onSubmit()` directly instead of clicking the button. 

**It is important to recognize this kind of errors when writing our tests to avoid "false positives". Having a green test does not necessarily mean that is working as expected.**

So, if we fix our test replacing `component.onSubmit()` by clicking the button, we should have again a failing test:

```javascript
it('should render email validation message when formControl is submitted and invalid', () => {
  const elements: HTMLElement = fixture.nativeElement;
  expect(elements.querySelector('#email-error')).toBeFalsy();

  elements.querySelector('button').click();

  fixture.detectChanges();
  expect(elements.querySelector('#email-error')).toBeTruthy();
  expect(elements.querySelector('#email-error').textContent).toContain(
    'Please enter a valid email.'
  );
});
```

What is missing now to make this test green? Correct, we should call onSubmit from our form when clicking Sign In button by adding `(ngSubmit)="onSubmit()"` to our form.

```html
<form [formGroup]="form" (ngSubmit)="onSubmit()">
  <input id="email" type="email" placeholder="Your email" />
  <span *ngIf="submitted && form.controls.email.invalid" id="email-error">
    Please enter a valid email.
  </span>
  <input id="password" type="password" placeholder="********" />
  <button type="submit">Sign in</button>
</form>

```

Lastly, let's do the same for our password input.

```javascript
it('should render password validation message when formControl is submitted and invalid', () => {
  const elements: HTMLElement = fixture.nativeElement;
  expect(elements.querySelector('#password-error')).toBeFalsy();

  elements.querySelector('button').click();

  fixture.detectChanges();
  expect(elements.querySelector('#password-error')).toBeTruthy();
  expect(elements.querySelector('#password-error').textContent).toContain(
    'Please enter a valid password.'
  );
});

```

Before proceeding, check that the test is failing.
Good, now we need the html part to make it green:

```html
<span *ngIf="submitted && form.controls.password.invalid" id="password-error">
  Please enter a valid password.
</span>
```

### Fifth step: Styling

Now it is time to make our login form look nice! You could use plain css or your preferred css framework. In this tutorial, we are going to use [TailwindCSS](https://tailwindcss.com/), and you could read this post on how to install it:

{% link https://dev.to/seankerwin/angular-8-tailwind-css-guide-3m45 %}

And for styling our form, we could just follow official doc:
[Login Form](https://tailwindcss.com/components/forms/#login-form)


Our final result:

![Login result screenshot](https://thepracticaldev.s3.amazonaws.com/i/pcvtgnyvfy2f4qewexz3.jpg)

The next post is going to be the authentication service and how to invoke it using this form we've just built. 

{% link https://dev.to/jpblancodb/angular-series-creating-an-authentication-service-with-tdd-4p2k %}

If you have any doubt, you could leave a comment or contact me via [Twitter](https://twitter.com/jpblancodb). I'm happy to help!