Angular 2 testing

Jesús Rodríguez

Consultor en IdeaBlade

Agenda

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

Un pequeño repaso...

En Angular tenemos los NgModule:

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

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

TestBed

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

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.

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 "golden race"', () => {
    expect(pipe.transform('golden race')).toBe('Golden race');
  });

  it('capitalizes the entire sentence', () => {
    expect(pipe.transform('golden race', true)).toBe('Golden Race');
  });
});
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'));

Angular 2 testing

By Jesus Rodriguez

Angular 2 testing

  • 466