Angular series: Creating an authentication service with TDD
Tags: angular, tdd, javascript, tutorial
Published: 2019-11-20 14:15:15

---
title: Angular series: Creating an authentication service with TDD
published: true
description: How to create an authentication service in angular with TDD
tags: angular, TDD, javascript, tutorial
series: angular
---


Let's continue with the Angular series, now is time for implementing the service for doing the authentication.

The final project could be found in my personal Github: [Angular series](https://github.com/JPBlancoDB/angular-series)

If you missed the previous post, we created the Login component.

{% link https://dev.to/jpblancodb/angular-series-creating-a-login-with-tdd-3jkl %}

Before starting, let's run our tests and verify that everything is passing:

```
npm run test
```

If everything is still green we can continue otherwise, we need to fix it first. 

### First step: Add a test 

Let's start by adding a test in our Login component to assert that after submitting our form, we are going to call the authentication service.

```javascript
  //login.component.spec.ts
  it('should invoke auth service when form is valid', () => {
    const email = component.form.controls.email;
    email.setValue('test@test.com');
    const password = component.form.controls.password;
    password.setValue('123456');
    authServiceStub.login.and.returnValue(of());

    fixture.nativeElement.querySelector('button').click();

    expect(authServiceStub.login.calls.any()).toBeTruthy();
  });
```

As you noticed, is broken but don't worry! What happened? We've just added `authServiceStub` that is not declared and `of` that is not imported. Let's fix it all.

Import `of` from `rxjs` by doing (probably if you use an IDE or vscode, this could be done automatically):

```javascript
import { of } from 'rxjs';
```

Now, let's continue by fixing `authServiceStub`, we need to declare this in our `beforeEach`:


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

  const authServiceStub: jasmine.SpyObj<AuthService> = jasmine.createSpyObj(
    'authService',
    ['login']
  );

  beforeEach(async(() => {
    TestBed.configureTestingModule({
      declarations: [LoginComponent],
      imports: [ReactiveFormsModule],
      providers: [
        {
          provide: AuthService,
          useValue: authServiceStub
        }
      ]
    }).compileComponents();
  }));
```

Basically, what we are doing here is to use our stub instead of the real service when unit testing our login component.

But, why is it still failing? You're right! Because `AuthService` does not exist... yet. 

We could use schematics for this. So, open your terminal:

```
ng generate service login/auth
```

This will generate the `auth.service.ts` and the base `auth.service.spec.ts` in our `login` folder.

Now, is time for importing the created service.

```javascript
import { AuthService } from './auth.service';
```

Lastly, we'll see a new error, to fix it, we should add the login method to our authentication service.

```javascript
//auth.service.ts
login(): Observable<string> {
  throw new Error('not implemented');
}
```

Done! We should have our failing test :sunglasses:! But, you should have an error with your `auth.service` test. For now, just remove the default test, we are going to come back to this later.

It's time for making our test green:

```javascript
//login.component.ts
onSubmit() {
  this.submitted = true;

  if (this.form.valid) {
    this.authService.login().subscribe(
      res => console.log(res),
      error => console.log(error)
    );
  }
}
```

But, as you noticed, we have a green test but this service is not useful if we don't pass as a parameter to the login function the email and the password. What we could do? Yes, a test! 

We have two options, or we add an extra assertion to our test or we create a new test to verify that our stub is being called with correct parameters. For simplicity, I will just add an extra assertion, so our test would look like this:

```javascript
//login.component.spec.ts
it('should invoke auth service when form is valid', () => {
  const email = component.form.controls.email;
  email.setValue('test@test.com');
  const password = component.form.controls.password;
  password.setValue('123456');
  authServiceStub.login.and.returnValue(of());

  fixture.nativeElement.querySelector('button').click();

  expect(authServiceStub.login.calls.any()).toBeTruthy();
  expect(authServiceStub.login).toHaveBeenCalledWith(
    email.value,
    password.value
  );
});

```

Yep, again to our beautiful red test! Remember our *Red, Green, Refactor*: [The cycles of TDD](https://blog.cleancoder.com/uncle-bob/2014/12/17/TheCyclesOfTDD.html)) 

Hands-on! Let's fix it.

```javascript
//login.component.ts
this.authService
    .login(this.form.value.email, this.form.value.password)
    .subscribe(
       res => console.log(res),
       error => console.log(error)
    );
```

And we need to add email and password parameters to our login function in the service.

```javascript
//auth.service.ts
login(email: string, password: string): Observable<string> {
  throw new Error('not implemented');
}
```

Done! Check that you have all the tests passing. If this is not the case, go back and review the steps or add a comment!


### Second step: Authentication Service
 
It's time for creating our first test in `auth.service.spec.ts`. One remark, in this case, to avoid confusion I will avoid using jasmine-marbles for testing observables, you could read more here: [Cold Observable](https://angular.io/guide/testing#cold-observable). But, don't worry I'll write a separate post only for explaining it in deep.

How do we start? Exactly! By creating the test, and here I'll cheat a little bit because I already know that we need HttpClient dependency, so:

```javascript
//auth.service.spec.ts
import { AuthService } from './auth.service';
import { HttpClient } from '@angular/common/http';
import { of } from 'rxjs';

describe('AuthService', () => {
    it('should perform a post to /auth with email and password', () => {
      const email = 'email';
      const password = 'password';
      const httpClientStub: jasmine.SpyObj<HttpClient> = jasmine.createSpyObj(
        'http',
        ['post']
      );
      const authService = new AuthService(httpClientStub);
      httpClientStub.post.and.returnValue(of());

      authService.login(email, password);

      expect(httpClientStub.post).toHaveBeenCalledWith('/auth', { email, password });
    });
});

```

This will cause some errors. We first need to inject `HttpClient` into `AuthService`:

```javascript
//auth.service.ts
constructor(private httpClient: HttpClient) {}
```

Try again! What did you see? Our red test! Once more :smiley:. 
This implementation is quite easy, let's do it:

```javascript
  //auth.service.ts
  login(email: string, password: string): Observable<string> {
    return this.httpClient.post<string>('/auth', {
      email,
      password
    });
  }
```

And that is it! We should have our working service with all our tests green! :tada::tada::tada:

If you want to manually try this and to avoid creating the server, we could just add an interceptor (remember to add it as a provider in your `app.module`):

```javascript
import { Injectable } from '@angular/core';
import {
  HttpInterceptor,
  HttpEvent,
  HttpHandler,
  HttpRequest,
  HttpResponse,
  HTTP_INTERCEPTORS
} from '@angular/common/http';
import { Observable, of } from 'rxjs';
import { delay } from 'rxjs/operators';

@Injectable()
export class FakeServerInterceptor implements HttpInterceptor {
  intercept(
    req: HttpRequest<any>,
    next: HttpHandler
  ): Observable<HttpEvent<any>> {
    if (req.url.endsWith('/auth')) {
      return this.authenticate();
    }

    return next.handle(req);
  }

  authenticate(): Observable<HttpResponse<any>> {
    return of(
      new HttpResponse({
        status: 200,
        body: 'jwt-token'
      })
    ).pipe(delay(1000));
  }
}

export const fakeServerProvider = {
  provide: HTTP_INTERCEPTORS,
  useClass: FakeServerInterceptor,
  multi: true
};

```

Lastly, if you were wondering how to do it with jasmine-marbles, would be something like this:

```javascript
//auth.service.spec.ts
  it('should perform a post to /auth with email and password', () => {
    const serverResponse = 'jwt-token';
    const email = 'email';
    const password = 'password';
    const httpClientStub: jasmine.SpyObj<HttpClient> = jasmine.createSpyObj(
      'http',
      ['post']
    );
    const authService = new AuthService(httpClientStub);
    httpClientStub.post.and.returnValue(cold('a', {a: serverResponse}));

    const response = authService.login(email, password);

    expect(response).toBeObservable(cold('a', {a: serverResponse}));
    expect(httpClientStub.post).toHaveBeenCalledWith('/auth', { email, password });
  });
```

If you have any doubt you could add a comment or ask me via [Twitter](https://twitter.com/jpblancodb)