Angular Testing

Jesús Rodríguez

Foxandxss

Agenda

  • Un pequeño repaso...
  • UNIT testing y mocks.
  • TestBed.
  • Testeando componente.
  • Testeando servicio.
  • Testeando tubería.
  • Ejemplo real.

Un pequeño repaso...

En Angular tenemos los NgModule:

@NgModule({
  imports: [ BrowserModule, HttpModule ],
  declarations: [ AppComponent, TeamsComponent, CapitalizePipe ],
  providers: [ TeamService ],
  bootstrap: [ AppComponent ]
})
export AppModule {}

Que es como una caja donde registramos las distintas piezas de nuestra aplicación...

UNIT testing y mocks

La regla de oro de los unit test, es que una unidad (unit) tiene que ser testeada sin necesitar ninguna de sus dependencias.

class TeamServiceSpy {
  getTeams = jasmine.createSpy('getTeams').and.callFake(() => {
    return Observable.of([{ id: 0, name: 'Madrid'}, { id: 1, name: 'Malaga' }]);
  });
}

Un ejemplo de mock de un servicio sería:

Eso quiere decir, que si una unidad tiene dependencias, tenemos que reemplazarlas por mocks.

TestBed

En testing, tenemos nuestros propios NgModule:

TestBed.configureTestingModule({
  imports: [ ... ],
  declarations: [ ... ],
  providers: [ ... ],
  ...
});

Nuestra propia caja vacía que podemos configurar como necesitemos.

TestBed es la utilidad principal de Angular para escribir nuestros tests.

Testeando componente

Tenemos un componente TeamsComponent que inyecta TeamService como dependencia y muestra un listado de equipos:

@Component({
  selector: 'gr-teams',
  template: `
    <ul>
      <li *ngFor="let team of teams">
        {{team.id}} - {{team.name}}
      </li>
    </ul>
  `
})
export class TeamsComponent implements OnInit {
  teams: any;
  
  constructor(private teamService: TeamService) { }
  
  ngOnInit() {
    this.teamService.getTeams().subscribe(teams => this.teams = teams);
  }
}

Testeando componente

Lo primordial es coger ese módulo, esa caja vacía y darle lo mínimo que nos hace falta, TeamsComponent y un mock de TeamService.

TestBed.configureTestingModule({
  declarations: [ TeamsComponent ],
  providers: [ ¿ ? ]
});

¿Qué ponemos en provider? No podemos poner el original y si le damos el mock no sabrá para qué.

providers: [{ provide: TeamService, useClass: TeamServiceSpy }]

Testeando componente

Una vez tenemos el módulo configurado, creamos el componente:

let fixture: ComponentFixture<TeamsComponent>;

fixture = TestBed.createComponent(TeamsComponent);

Y guardamos una referencia a la instancia en si:

let instance: TeamsComponent;

instance = fixture.componentInstance;

Por último, vamos a pedirle al inyector la instancia del servicio:

let teamService: TeamService;

teamService = fixture.debugElement.injector.get(TeamService);

Testeando componente

Al ejecutar ngOnInit​, ¿está recibiendo los equipos?

it('fetches all teams', () => {
  instance.ngOnInit();
  expect(instance.teams.length).toBe(2);
  expect(teamService.getTeams).toHaveBeenCalled();
});

¿Y los muestra por pantalla?

it('renders the teams on the screen', () => {
  fixture.detectChanges();
  const li = fixture.debugElement.queryAll(By.css('li'));
  expect(li.length).toBe(2);
});

Testeando componente

Como alternativa, se puede usar un componente de prueba que haga de wrapper al componente bajo test.

@Component({
  selector: 'gr-test',
  template: '<gr-team [home]="home"></gr-team>'
})
class TestComponent {
  home = { id: 0, name: 'Malaga' };
}

Gracias a este wrapper, podríamos testear la API (entradas y salidas) de nuestro componente.

Testeando servicio

Supongamos que tenemos el siguiente servicio:

export class TeamService {
  constructor(private http: Http) { }
  
  getTeams() {
    return this.http.get('api/teams')
      .map((res) => res.json().data);
  }
  
  getTeam(id: number) {
    return this.http.get(`api/teams/${id}`)
      .map((res) => res.json().data);
  }
}

Testeando servicio

¿Cómo hacemos mock de HTTP?

providers: [
  TeamService,
  {
    provide: Http,
    useFactory: (backend, options) => {
      return new Http(backend, options);
    },
    deps: [MockBackend, BaseRequestOptions]
  },
  MockBackend,
  BaseRequestOptions
]

Testeando Servicio

Inyectando nuestras dependencias antes de cada test:

beforeEach(inject([TeamService, MockBackend],
            (service: TeamService, backend: MockBackend) => {
  showService = service;
  mockBackend = backend;

  fakeTeams = {
    data: [
      { id: 0, name: 'Malaga' },
      { id: 1, name: 'Madrid' }
    ]
  };
}));

Y creando algunos equipos de ejemplo...

Testeando Servicio

... que habría que proveer cuando hagan falta.

it('gets the list of shows', () => {
  mockBackend.connections.subscribe((connection) => {
    connection.mockRespond(new Response(new ResponseOptions({
      body: JSON.stringify(fakeTeams)
    })));
  });

  showService.getShows().subscribe((shows) => {
    expect(shows.length).toBe(2);
    expect(shows[0].name).toBe('Malaga');
    expect(shows[1].name).toBe('Madrid');
  });
});

Testeando tuberías

Y sin TestBed ;)

describe('CapitalizePipe', () => {
  let pipe = new CapitalizePipe();

  it('capitalizes "angular is nice"', () => {
    expect(pipe.transform('angular is nice')).toBe('Angular is nice');
  });

  it('capitalizes the entire sentence', () => {
    expect(pipe.transform('angular is nice', true)).toBe('Angular Is Nice');
  });
});
import { Pipe, PipeTransform } from '@angular/core';

@Pipe({ name: 'capitalize'})
export class CapitalizePipe implements PipeTransform {
  transform(value: string) {
    return value[0].toUpperCase() + value.substring(1); 
  }
}

Usando by

Para testeo de componentes, tenemos acceso a By:

import { By } from '@angular/platform-browser';
fixture.debugElement.query(By.css('foo'));

fixture.debugElement.queryAll(by.directive('directive'));

GRACIAS

Angular testing

By Jesus Rodriguez

Angular testing

  • 457