Introdução ao Tema
Neste post vou te mostrar um exemplo de como podemos utilizar o Spectator para criar Mocks ou Stubs e construir os cenários de teste, simulando uma request HTTP de uma API externa em um sistema que estamos desenvolvendo.
Neste exemplo usei a VIACEP como exemplo para ser consumido.
Vá direto ao ponto
- Apresentação do Problema
- Configuração
- Exemplo de retorno da Request
- Modelo de Resposta da Requisição
- Construindo Cenários e Declarando Dependências
- Definição de Mock ou de retorno Fake
- Construção da Resposta Fake
- Validação de regras de negócios
- Verificações
- Exemplo Completo
- Para executar o exemplo
- Resultado Final
- Inicializando o Projeto
- Link para o repositório do exemplo
Apresentação do Problema
Ao implementar um cenário de teste que necessita fazer uma requisição à um serviço externo precisamos construir uma resposta falsa (fake) para suprir a resposta de um serviço externo que não estará disponível no momento do teste unitário.
Para este exemplo irei utilizar como exemplo o servico de API ViaCEP
Configuração
Para adicionar a dependência do spectator
npm install @ngneat/spectator --save-dev
Exemplo de retorno da Request
Para começar, se quiser ter uma ideia de como será o retorno da requisição que iremos trabalhar basta executar o comando abaixo no terminal para verificar o retorno do nosso exemplo.
curl -X GET http://viacep.com.br/ws/38660000/json/
Modelo de resposta da requisição
O resultado da execução será um modelo JSON como este:
{
"cep": "38660-000",
"logradouro": "",
"complemento": "",
"bairro": "",
"localidade": "Buritis",
"uf": "MG",
"ibge": "3109303",
"gia": "",
"ddd": "38",
"siafi": "4185"
}
Construindo Cenários e Declarando Dependências
Atenção!!
Não se preocupe em copiar os códigos inicialmente pois mostrarei um exemplo mais completo no final..
O primeiro passo para a construção do cenário é suprir a estrutura com todas as dependência da classe que será testada.
No nosso exemplo temos uma dependência com "FormsModule" e outra dependência com "ListCepAPI" que é uma dependência indireta através do ListCepService.
const createComponent = createComponentFactory({
component: ListCepComponent,
imports: [FormsModule],
mocks: [
ListCepAPI,
],
detectChanges: false
});
const createService = createServiceFactory({
service: ListCepService,
});
Definição de Mock ou de retorno Fake
Nesta etapa iremos definir qual será o retorno que o servidor devolveria em um caso real. Como nesta etapa não teremos uma infraestrutura disponível iremos devolver um resultados "fabricado".
beforeEach(() => {
spectatorComponent = createComponent();
spectatorService = createService();
component = spectatorComponent.component;
service = spectatorComponent.inject<ListCepService>(ListCepService);
apiMocked = spectatorService.inject<ListCepAPI>(ListCepAPI);
apiMocked.findAddress.andReturn(Promise.resolve(fakeResponse));
});
Construção da resposta Fake
Observe que foi construído um objeto expectData que será utilizado para verificação do resultado e outro objeto fake chamado "fakeResponse" para ser devolvido em
apiMocked.findAddress.andReturn(Promise.resolve(fakeResponse)
// Fake Object
const fakeResponse: Address = {
cep: '01001-000',
logradouro: 'Praça da Sé',
complemento: 'lado ímpar',
bairro: 'Sé',
localidade: 'São Paulo',
uf: 'SP',
ibge: '3550308',
gia: '1004',
ddd: '11',
siafi: '7107'
};
// Dados Esperados
const expectData: Address = {
cep: '01001-000',
logradouro: 'Praça da Sé',
complemento: 'lado ímpar',
bairro: 'Sé',
localidade: 'São Paulo',
uf: 'SP',
ibge: '3550308',
gia: '1004',
ddd: '11',
siafi: '7107',
enderecoCompleto: 'Praça da Sé, Sé, São Paulo'
};
Validação de regras de negócios
Um exemplo de uma regra de negócios seria o campo de enderecoCompleto que não existe no retorno da API porém ocorre uma transformação dos dados recebidos para construir este campo. Neste caso, por exemplo, o campo poderia ser um calculo de frete ou qualquer outro tipo de transformação dos dados recebidos através da chamada ao serviço externo.
Verificações
Após a construção do cenário nosso foco deverá ser na construção das nossas verificações ou asserções.
🚧 🚨 Atenção para o exemplo do assert 'should check service result' para este caso resolvi deixar um console.log() apenas para que veja um exemplo de como será o resultado. Porém, ao enviar para produção os testes NÃO deverão conter comandos de display.
it('should exist component', () => {
expect(component).toBeTruthy();
});
it('should exist service', () => {
expect(service).toBeTruthy();
});
it('should exist apiMocked', () => {
expect(apiMocked).toBeTruthy();
});
it('should check service result', async () => {
const result = await service.getAddress();
console.log(result);
expect(dataExpect).toEqual(result);
});
it('should click button', async () => {
spectatorComponent.click('#searchAddress');
spectatorComponent.detectChanges();
const enderecoCompleto = spectatorComponent.query('#enderecoCompleto').textContent;
const enderecoCompletoExpected = 'Endereço completo: Praça da Sé, Sé, São Paulo'
expect(enderecoCompletoExpected).toEqual(enderecoCompleto);
});
Exemplo completo:
import { FormsModule } from '@angular/forms';
import { SpyObject } from '@ngneat/spectator';
import {
Spectator,
createComponentFactory,
createServiceFactory,
SpectatorService,
} from '@ngneat/spectator/jest';
// quem estiver executando os tetes apenas com o Karma.js o jest deverá ser removido do caminho
//} from '@ngneat/spectator/';
import { Address } from './address.model';
import { ListCepAPI } from './list-cep.api';
import { ListCepComponent } from './list-cep.component';
import { ListCepService } from './list-cep.service';
describe('ListCepComponent', () => {
const createComponent = createComponentFactory({
component: ListCepComponent,
imports: [FormsModule],
mocks: [
ListCepAPI,
],
detectChanges: false
});
const createService = createServiceFactory({
service: ListCepService,
});
let spectatorComponent: Spectator<ListCepComponent>;
let spectatorService: SpectatorService<ListCepService>;
let component: ListCepComponent;
let service: SpyObject<ListCepService>;
let apiMocked: SpyObject<ListCepAPI>;
beforeEach(() => {
spectatorComponent = createComponent();
spectatorService = createService();
component = spectatorComponent.component;
service = spectatorComponent.inject<ListCepService>(ListCepService);
apiMocked = spectatorService.inject<ListCepAPI>(ListCepAPI);
apiMocked.findAddress.andReturn(Promise.resolve(fakeResponse));
});
it('should exist component', () => {
expect(component).toBeTruthy();
});
it('should exist service', () => {
expect(service).toBeTruthy();
});
it('should exist apiMocked', () => {
expect(apiMocked).toBeTruthy();
});
it('should check service result', async () => {
const result = await service.getAddress();
console.log(result);
expect(dataExpect).toEqual(result);
});
it('should click button', async () => {
spectatorComponent.click('#searchAddress');
spectatorComponent.detectChanges();
const enderecoCompleto = spectatorComponent.query('#enderecoCompleto').textContent;
const enderecoCompletoExpected = 'Endereço completo: Praça da Sé, Sé, São Paulo'
expect(enderecoCompletoExpected).toEqual(enderecoCompleto);
});
const fakeResponse: Address = {
cep: '01001-000',
logradouro: 'Praça da Sé',
complemento: 'lado ímpar',
bairro: 'Sé',
localidade: 'São Paulo',
uf: 'SP',
ibge: '3550308',
gia: '1004',
ddd: '11',
siafi: '7107'
};
const dataExpect: Address = {
cep: '01001-000',
logradouro: 'Praça da Sé',
complemento: 'lado ímpar',
bairro: 'Sé',
localidade: 'São Paulo',
uf: 'SP',
ibge: '3550308',
gia: '1004',
ddd: '11',
siafi: '7107',
enderecoCompleto: 'Praça da Sé, Sé, São Paulo'
};
});
Para executar o exemplo
git clone https://github.com/rogeriofonseca/angular-spectator-example.git
cd angular-spectator-example
npm install
npm run test:watch
Resultado Final
Ao executar os testes executando o seguinte comando na raiz do projeto você poderá observar o seguinte resultado.
npm run test:watch
🚧 🚨 Lembrando que apenas para fins de demonstração resolvi deixar um console.log() no código para demonstrar a saída do resultado.
console.log
{ cep: '01001-000',
logradouro: 'Praça da Sé',
complemento: 'lado ímpar',
bairro: 'Sé',
localidade: 'São Paulo',
uf: 'SP',
ibge: '3550308',
gia: '1004',
ddd: '11',
siafi: '7107',
enderecoCompleto: 'Praça da Sé, Sé, São Paulo' }
at src/app/list-cep/list-cep.component.spec.ts:59:13
PASS src/app/list-cep/list-cep.component.spec.ts
ListCepComponent
✓ should exist component (93 ms)
✓ should exist service (27 ms)
✓ should exist apiMocked (27 ms)
✓ should check service result (51 ms)
✓ should click button (510 ms)
Test Suites: 1 passed, 1 total
Tests: 5 passed, 5 total
Snapshots: 0 total
Time: 4.367 s, estimated 5 s
Ran all test suites related to changed files.
Watch Usage
› Press a to run all tests.
› Press f to run only failed tests.
› Press p to filter by a filename regex pattern.
› Press t to filter by a test name regex pattern.
› Press q to quit watch mode.
› Press Enter to trigger a test run.
Inicializando o Projeto (run start)
Comando para inicializar o projeto
npm run start
O resultado poderá ser visualizado no browser no endereço
http://localhost:4200/
Top comments (4)
Parabéns Dom!!!
Excelente.
Parabéns Rogério, excelente post. 👏🏻👏🏻👏🏻
Parabéns pelo post, Rogério! 👏🏽👏🏽
Caramba! super descritivo e direto ao ponto!!!