Angular - Introdução aos Testes
Tags: angular, beginners, testing
Published: 2020-04-21 02:15:22

Hoje iniciaremos uma série de posts sobre [testes em Angular](https://angular.io/guide/testing).

Outros posts da série:

- Parte 1 - Introdução aos Testes
- [Parte 2 - Testes de Pipes e Services](https://dev.to/wesleyegberto/angular-testes-de-pipes-e-services-41jm)
- [Parte 3 - Testes de Componentes](https://dev.to/wesleyegberto/angular-introducao-aos-testes-2m7f)

Caso você não conheça ou seja novo em Angular recomendo uma das melhores video-aulas que existe em português ministradas pela Loiana Groner, [veja aqui](https://loiane.training/curso/angular).

Angular nos fornece uma variedade de APIs para efetuarmos testes dos nossos componentes de forma fácil e rápida.

Quando criamos um projeto utilizando o Angular CLI executando o comando `ng new` já é configurado tudo que é preciso para testar um projeto Angular.

As ferramentas de testes utilizadas pelo Angular por padrão são:

* [Karma](https://karma-runner.github.io/): test runner no browser;
* [Jasmine](https://jasmine.github.io/): framework para testes unitários e integrados, também fornece suporte para mocks.

Também é possível utilizar outras ferramentas como: Jest, Mocha/Chai, Sion, TestDouble, Wallaby, Cypress. Mas é necessário configuração manual.

## Karma

Karma é um framework para rodar testes JavaScript que nos permite ser bastante produtivos ao prover um ambiente totalmente configurado (e que pode ser customizado) e um rápido feedback dos testes.

## Jasmine

Jasmine é um framework [BDD (behavior-driven development)](https://pt.wikipedia.org/wiki/Behavior_Driven_Development) para testes de código JavaScript. Ele não requer DOM para rodar e não possui nenhuma dependência.

Devido ao BDD e sua API fluente, sua sintaxe se torna bem limpa e extramente fácil de ler.

Nos fornece uma série de API para validar valores e objetos, efetuar testes unitários e integrados, criar mocks para nos ajudar a isolar nossos testes, etc.

O framework nos fornece uma série de APIs:

* matchers: funções para validar valores e objetos;
* funções setup: funções para preparar os objetos que usaremos nos testes (ex.: objeto que tenha inicialização seja muito complexa);
* funções teardown: funções para fazer a limpeza ou pós-processamento dos objetos utilizados nos testes (ex.: limpar recursos compartilhados ou complexos em um testes integrado – banco de dados in-memory);
* mocks: objetos dummy que podem ser configurados conforme o teste demandar.

### Exemplo de Teste no Jasmine

A seguir temos um exemplo de estrutura de teste em Jasmine com os métodos estão comentados com a explicação de uso:

```js
/**
 * A função `describe` define um conjunto de especificações que precisam
 * ser testadas.
 * No testes do Angular, geralmente, o cenário estará vinculado a uma
 * estrutura do Angular: um componente, service, pipe, etc.
 */
describe('Meu Cenario', () => {
    // system under test (unidade que será testada teste)
    let sut: any = null;

    /**
     * Função para configurarmos algo que será compartilhado
     * entre todos os testes.
     */
    beforeAll(() => {
        console.log('Roda apenas uma vez antes de todos os testes');
    });

    /**
     * Função para configurarmos os objetos que usaremos em cada teste.
     * É importante sempre iniciarlizar aqui para que sempre seja
     * resetado antes de cada teste, assim, evitando que um teste
     * influencie outro.
     */
    beforeEach(() => {
        console.log('Roda uma vez antes de cada teste');
        sut = {};
    });

    /**
     * Define uma especificação única que será testada, dentro de um cenário BDD
     * podemos ter vários testes (funções `it`) ou até mesmo outros cenários (funções `describe`).
     * BDD recomenta que os testes sempre iniciem com `deveria` (traduzido de `should`).
     */
    it('should be true if true', () => {
        // Montagem do cenário
        sut.a = false;
        // Ação
        sut.a = true;
        // Asserção
        expect(sut.a).toBe(true);
    });

    /**
     * Função para limparmos algo depois de cada teste.
     */
    afterEach(() => {
      console.log('Roda uma vez depois de cada teste');
    });

    /**
     * Função para limparmos algo compartilhado entre todos os testes.
     */
    afterAll(() => {
      console.log('Roda apenas uma vez depois de todos os testes');
    });
});
```

É importante ter o sufixo `.spec.ts` porque o runner fará uma pesquisa por ele.

Um teste deveria ser uma história completa contida na função `it`. Não deveria ser preciso ficar olhando ao redor para entender o teste.

Dicas:

* mova código menos interessante de setup para dentro da função beforeEach;
* mantenha o setup crítico dentro da especificação em teste (função it);
* a especificação em teste (função it) deveria conter as três partes do teste: arranjo da pré-condição; ação e a asserção.

## Tipos de Testes

* Unit tests:
  * Testa uma unidade de código (pode ser função, pipe, service, classe, componente);
  * Tipos de testes unitários no Angular:
    * Isolado: testamos uma única classe ou função onde instanciamos manualmente passando os argumentos necessários;
    * Integrado: testamos uma unidade através da criação de um module do Angular (por exemplo, para testar o template de um componente), pode ser divido em:
      * Shallow: testamos apenas um componente (sem os filhos);
      * Deep: testamos o componente com os filhos.
* Integration tests:
  * Testa um conjunto de unidades de código que juntas entragam uma funcionalidade.
* End to End (E2E) test:
  * Aplicação live running;
  * Utiliza um browser com açÕes automatizadas (webdriver).

### Mocks

Mocks nos ajudam a garantir que estamos testando uma unidade de forma isolada.

Mock permite simular uma dependência que a unidade precisa para funcionar de forma completa.

Tipos de mock:

* Dummies: objeto para ocupar uma dependência;
* Stubs: objeto que tem um comportamento controlável, definimos nele o retorno necessário para completar o cenário que
estamos testando;
* Spies: objeto que rastreia quais métodos deles foram chamados, com quais argumentos e quantas vezes, usamos ele para
garantir que o comportamento esperado da unidade está sendo executado;
* True mocks: objeto que usamos para saber se foi utilizado de uma forma bem específica (se determinado método foi chamado,
quais argumentos, quais não deveriam ser chamados, etc), são mais complexos de montar mas ajudam a garantir o comportamento
esperado.

## Angular Tests

No Angular, os arquivos de testes têm o mesmo nome da unidade (seja uma pipe, service, componente ou simples classe) que
estamos testando mas com o sufixo `.spec.ts`, e fica na mesma pasta da unidade que estamos testando.

Os testes isolados são bem diretos vistos que são apenas classes. Normalmente pipes, services e componentes terão uma estrutura de teste similar.
Às vezes um mock será necessário para ajudar a isolar a unidade.

### Primeiro Teste

Dados a pipe abaixo:

```ts
// strength.pipe.ts
import { Pipe, PipeTransform } from '@angular/core';

@Pipe({ name: 'strength' })
export class StrengthPipe implements PipeTransform {
  transform(value: number): string {
    if(value < 10) {
      return value + " (weak)";
    } else if(value >= 10 && value < 20) {
      return value + " (strong)";
    } else {
      return value + " (unbelievable)";
    }
  }
}
```

Podemos escrever um teste abaixo:

```ts
// strength.pipe.spec.ts
import { StrengthPipe } from './strength.pipe';

// declaramos a especificação de teste da Pipe
describe('StrengthPipe', () => {
  let pipe: StrengthPipe;

  // prepara o objeto de teste
  beforeEach(() => {
    // instancia a Pipe que iremos testar
    pipe = new StrengthPipe();
  });

  it('should display weak if strength is 5', () => {
    expect(pipe.transform(5)).toBe('5 (weak)');
  });

  it('should display strong if strength is 10', () => {
    expect(pipe.transform(10)).toBe('10 (strong)');
  });
});
```

Em um projeto criado a partir do Angular CLI basta executarmos `npm test` pelo terminal para rodar os testes.

No [próximo post](https://dev.to/wesleyegberto/angular-testes-de-pipes-e-services-41jm) criaremos alguns testes de componentes.