# Angular Testing

## Jasmine y Karma

Angular usa Jasmine como framework de pruebas y Karma como *test Runner.* A partir de la versión 15 de Angular se eliminaron algunos archivos de configuración para hacer más fácil el entendimiento del framework para nuevos desarrolladores. Karma.conf.js ya no se crea por default con el *ng new*, sin embargo lo podemos agregar con el siguiente comando:

```typescript
ng generate config karma
```

Automáticamente karma va a leer todos los archivos del proyecto que acaben con `spec.ts` para ejecutar las pruebas.

## Estructura

```typescript
describe('HomeComponent', () => {
  let component: HomeComponent;

  it('should create', () => {
    expect(component).toBeTruthy();
  });
  
});
```

`describe` - define una suite de tests. Una colección de tests. Recibe dos parámetros, un `string` con el nombre de la suite y una `function()` donde se definen los tests.

`it` - define un test en particular. Recibe como parámetro el nombre del test y una función a ejecutar por el test.

`expect` - Lo que esperar recibir ese test. Con `expect` se hace la comprobación del test.

### Se puede seguir unos pasos muy utiles

**Arrange:(Arreglar)**. Se establece el estado inicial, conocida como el sujeto a probar. Aquí se inicializan variables, importaciones. Se crea el ambiente a probar.

**Act (Actuar)**: Se generan acciones o estímulos. Se llaman métodos, o se simulan clicks por ejemplo

**Assert (Afirmar):** observar el comportamiento. Los resultados son los esperados. Eje: Que algo cambie, se incremente, o no suceda nada.

```typescript
import { Calculator } from './calculator';

describe('Test for Calculator', () => {
  it('#multiply should return a nine', () => {
    //Arrange
    const calculator = new Calculator();
    //Act
    const rta = calculator.multiply(3,3);
    //Assert
    expect(rta).toEqual(9);
  });
});
```

## Matchers

```typescript
//Comunes
.toBe();
.not.toBe();
.toEqual();

//Veracidad
.toBeNull()
.toBeUndefined()
.toBeDefined()
.toBeUndefined()
.toBeTruthy() 
.toBeFalsy() 

//Numeros
.toBeGreaterThan(3);
.toBeGreaterThanOrEqual(3.5);
.toBeLessThan(5);
.toBeLessThanOrEqual(4.5);

//Numeros decimales
expect(0.3).toBeCloseTo(0.3)

//Strings
.not.toMatch(/I/);
.toMatch(/stop/);

//Arrays
.toContain('milk');

//Ecepciones
myfunction.toThrow(Error);
```

## Reporte de covertura

Para poder generar un reporte de covertura (más visual y específico) que no estará en modo escucha continua, lo que debe hacer es correr el sigueinte comando en la términal

```
ng test --no-watch --code-coverage
```

Con esto podemos tener mas detalle, por ejemplo de que funciones no tienen pruebas etc.

<figure><img src="/files/arM0jA7lohjgaovcd1Gg" alt=""><figcaption></figcaption></figure>

Se pueden enfocar en ciertas pruebas u omitir agregando la letra `x` o `f` al inicio de las palabras claves de los test.

* con `fdescribe`ejecuta únicamente el suite de test
* con `xdescribe` se omite el suite de test
* con `fit` ejecuta el focus sobre un test
* con `xit` se omite un test

### Umbral mínimo de covertura

En el archivo `karma.conf.js` se agrega lo siguiente para actualizarlo y saber qué necesita ser cubierto con pruebas de acuerdo al umbral.

```typescript
// karma.conf.js
...
coverageReporter: {
      ...
      check: {
        global: {
          statements: 80,
          branches: 80,
          functions: 80,
          lines: 80
        }
      }
    },
...
```

Opcionalmente podemos tener un reporter adicional con mejoras visuales

Instalar Mocha Report

```
npm i karma-mocha-reporter --save-dev
```

Agregar en `karma.conf.js` en plugins

```json
...
plugins: [
      ...
      require('karma-mocha-reporter')
    ],
...
```

Cambiar reporters a

<pre class="language-json"><code class="lang-json"><strong>...
</strong><strong>    reporters: ['mocha'],
</strong><strong>...
</strong></code></pre>

## Testing a Servicios

<pre class="language-typescript"><code class="lang-typescript"><strong>// value.service.ts
</strong>export class ValueService {
  private value = 'my value';

  constructor() { }

  getValue() {
    return this.value;
  }

  setValue(value: string) {
    this.value = value;
  }

  getPromiseValue() {
    return Promise.resolve('promise value');
  }

  getObservableValue() {
    return of('observable value');
  }
}
</code></pre>

```typescript
// value.service.spec.ts
describe('ValueService', () => {
  let service: ValueService;

  // Cada que corre una prueba se ejecuta
  beforeEach(() => {
    service = new ValueService();
  });

  // Es recomendable primero validar si el servicio fue creado de una manera exitosa
  it('should be create', () => {
    expect(service).toBeTruthy();
  });

  describe('Tests for getValue', () => {
    it('should return "my value"', () => {
      expect(service.getValue()).toBe('my value');
    });
  });

  describe('Tests for setValue', () => {
    it('should change the value', () => {
      expect(service.getValue()).toBe('my value');
      service.setValue('change');
      expect(service.getValue()).toBe('change');
    });
  });

  describe('Tests for getPromiseValue', () => {
    it('should return "promise value" from promise with then', (done) => {
      service.getPromiseValue()
      .then((value) => {
        // assert
        expect(value).toBe('promise value');
        done();
      });
    });

    it('should return "promise value" from promise using async', async () => {
      const rta = await service.getPromiseValue();
      expect(rta).toBe('promise value');
    });
  });

});
```

Cada que colocamos un `it` estamos indicando un escenario de prueba, cada escenario de prueba debería manejarse de manera insolada. Esto significa que una prueba no debería afectar a la siguiente o a otra prueba.

Lo que hacemos es crear las instancias que ocupamos para cada prueba, por ejemplo la creación del servicio. Para optimizar el código se usa la función `beforeEach` que va a ejecutarse antes de cada prueba.

Cada vez que se corra una funcion asíncrona dentro de un *test* y se resuelva la promesa dentro de una función `then()`, por ejemplo, se recibirá una funcion para indicar manualmente que ha terminado la ejecución. Generalmenet se usa `done()`. Aunque lo recomendable es usar `async/await`.

## Test a Servicios con Dependencias

Recordar que si vamos a testear un servicio que tiene dependencias, nosotros no debemos usar las dependencias reales, es decir, ya que no le corresponde a nuestra prueba que las dependencias nos devuelval los valores deseados, nosotros solo debemos encargarnos de que nuestra prueba funcione, entonces podriamos hacer un `mock` de datos `fake` para completar nuestra prueba.&#x20;

El resto de dependencias tendrán sus propias pruebas ya, así que no nos deben de importar para la nuestra. Ejemplo:

Tenemos otro servicio.

```typescript
export class MasterService {
  constructor(
    private valueService: ValueService
  ) { }

  getValue() {
    return this.valueService.getValue();
  }
}
```

Para aislar completamente estas pruebas usamos el siguiente código.

```typescript
describe('MasterService', () => {
  it('should be return "other value" from the fake service', () => {
    const fakeValueService = new FakeValueService();
    const masterService = new MasterService(fakeValueService as unknown as ValueService);
    expect(masterService.getValue()).toBe('fake value');
  });
});

/* Este código no sería el correcto
describe('MasterService', () => {
  it('should be return "my value" from the real service', () => {
    const valueService = new ValueService();
    const masterService = new MasterService(valueService);
    expect(masterService.getValue()).toBe('my value');
  });
});
*/
```

y con un *fakeValueService*

```typescript
export class FakeValueService {
  constructor() { }

  getValue() {
    return 'fake value';
  }

  setValue (value: string) { }

  getPromiseValue() {
    return Promise.resolve('fake promise value');
  }
}
```

Aunque esto es una solución un poco mas correcta, en custion de escalabilidad y mantenimiento puede ser un problema al largo plazo.

Otra forma de hacerlo es con un objeto, por que los objetos en JS funcionan casi como clases por lo que puedo hacer que se pase directamente con un objeto en `master.service.spec.ts`.

```ts
...
  it('should be return "other value" from the fake object', () => {
    const fake = {getValue: () => 'fake from obj'};
    const masterService = new MasterService(fake as ValueService);
    expect(masterService.getValue()).toBe('fake from obj');
  });
...
```

Esto es especialemente util al momento de probar funcionalidades que por ejemplo se conectan a una API como google maps, etc.

## Spies

Para mejorar el tema del acceso a las dependencias que pueda tener un servicio podemos usar herramientas con las que cuenta *Jasmine,* como Spy.

Un *Spy* permite interceptar una función y **trackear** las llamadas a esta y sus argumentos, no necesariamente para obtener el valor que devuelve la función. Estos *Spies* solo existen en el bloque *describe* o *it* en el que fueron definidos, serán removidos luego de su implementación. Podemos definir que hara el *Spy* luego de ser invocado con and.

```typescript
...
  it('should call to getValue from ValueService', () => {
    const valueServiceSpy = jasmine.createSpyObj('ValueService', ['getValue']);
    valueServiceSpy.getValue.and.returnValue('fake value');
    const masterService = new MasterService(valueServiceSpy);
    expect(masterService.getValue()).toBe('fake value'); // ok
    // verifica si realmente se ejecuto la variable
    expect(valueServiceSpy.getValue).toHaveBeenCalled();
    expect(valueServiceSpy.getValue).toHaveBeenCalledTimes(1);
  });
...
```

En este contexto nosotros estamos espiando un servicio que está siendo llamado desde el servicio que estamos probando que es el de *MasterService.*

Mocking

> Son objetos simulados (pseudo-objetos, mock object, objetos de pega) a los que imitan el comportamiento de objetos reales de una forma controlada

Para este ejemplo usamos el concepto de *mocking* con *jasmine* a través de sus *spies.*

## TestBed

Angular tiene una suit que nos permite hacer pruebas en un entorno de una manera más sencilla.

Para configurarlo, sería:

```typescript
import { TestBed } from '@angular/core/testing';

describe('ValueService', () => {
...
  let service: ValueService;

  beforeEach(() => {
    TestBed.configureTestingModule({
      providers: [ ValueService ] //*
    });
    service = TestBed.inject(Value);
  });
...
}
```

Con esto se obtiene un contexto más limpio ya que estamos usando el patrón de inyección de dependencias. Los providers ayudan a aislar cierto servicio, componente etc. Para ser probado, ahi solo debemos meter los módulos que estemos testeando.

TestBed unido con los *spies* nos ayudara para resolver la inyección de dependencias. Tomando como ejemplo a master service

master.service.spec.ts

```ts
import { TestBed } from '@angular/core/testing';
import { MasterService } from './master.service';
import { ValueService } from './value.service';

describe('MasterService', () => {

    let masterService: MasterService;
    let valueServiceSpy: jasmine.SpyObj<ValueService>;

  beforeEach(() => {
    const spy = jasmine.createSpyObj('ValueService', ['getValue']);
    TestBed.configureTestingModule({
       providers: [ MasterService,
                  { provide: ValueService, useValue: spy }
                  ]
    });
    masterService = TestBed.inject(MasterService);
    valueServiceSpy = TestBed.inject(ValueService) as jasmine.SpyObj<ValueService>;
  });

  it('should be created', () => {
    expect(masterService).toBeTruthy();
  });
  
   it('should call to getValue from ValueService', () => {
    const valueServiceSpy = jasmine.createSpyObj('ValueService', ['getValue']);
    valueServiceSpy.getValue.and.returnValue('fake value');
    expect(masterService.getValue()).toBe('fake value');
    expect(valueServiceSpy.getValue).toHaveBeenCalled();
    expect(valueServiceSpy.getValue).toHaveBeenCalledTimes(1);
  });
});
```

## HttpClientTestingModule

La responsabilidad del *API* donde se conecta el *frontend* es del *backend*, es decir, para las pruebas unitarias no debemos tener en cuenta si el servidor está corriendo o no.

*HttpClientTestingModule* es un módulo de Angular que se utiliza para realizar pruebas unitarias de componentes y servicios que utilizan *HttpClient*, que es la clase de Angular encargada de realizar solicitudes *HTTP*.

```typescript
...
describe('ProductService', () => {
  let service: ProductService;
  let httpController: HttpTestingController;

  beforeEach(() => {
    TestBed.configureTestingModule({
      imports: [HttpClientTestingModule],
      providers: [ProductService],
    });
    service = TestBed.inject(ProductService);
    httpController = TestBed.inject(HttpTestingController);
  });

  it('should be created', () => {
    expect(service).toBeTruthy();
  });

  describe('Tests for getProducts', () => {
    it('should return any length', (doneFn) => {
      // Arrange
      const mockData: Words[] = [
        {
          id: '2o2',
          text_es: 'Valor',
          text_en: 'Value',
          rating: 10,
        },
      ];
      // Act
      service.getProducts('bar').subscribe((data) => {
        // Assert
        expect(data.length).toEqual(mockData.length);
        doneFn();
      });

      // http config*
      const url = `${environment.API_URL}/api/product`;
      const req = httpController.expectOne(url);
      req.flush(mockData);
      httpController.verify();

    });
  });
});
...
```

Al hacer una configuración `http` podemos decirle a la prueba que los valores los tome de un *mock* de datos en lugar de hacer una petición al servidor real. para este caso usamos `HttpTestingController.`

## Mocking

Para esto podemos usar la librería **Faker JS**

```bash
npm i @faker-js/faker --save-dev
```

```typescript
// product.mock.ts
import { faker } from '@faker-js/faker';

import { Product } from './product.model';

export const generateOneProduct = (): Product => {
  return {
    id: faker.datatype.uuid(),
    title: faker.commerce.productName(),
    price: parseInt(faker.commerce.price(), 10),
    description: faker.commerce.productDescription(),
    category: {
      id: faker.datatype.number(),
      name: faker.commerce.department()
    },
    images: [faker.image.imageUrl(), faker.image.imageUrl()]
  };
}

export const generateManyProducts = (size = 10): Product[] => {
  const products: Product[] = [];
  for (let index = 0; index < size; index++) {
    products.push(generateOneProduct());
  }
  return [...products];
}
```

y cambiamos el *mocking* en el archivo *product.service.spec.ts*

```typescript
...
// Arrange
const mockData: Words[] = generateManyProducts();
...
```

## Testing a Componentes

Por defecto Angular crea un componente parecido a este:

```typescript
import { ComponentFixture, TestBed } from '@angular/core/testing';

import { SearchComponent } from './search.component';
import { FormsModule } from '@angular/forms';

fdescribe('SearchComponent', () => {
  let component: SearchComponent;
  let fixture: ComponentFixture<SearchComponent>;

  beforeEach(async () => {
    await TestBed.configureTestingModule({
      imports: [FormsModule],
      declarations: [SearchComponent],
    }).compileComponents();

    fixture = TestBed.createComponent(SearchComponent);
    component = fixture.componentInstance;
    fixture.detectChanges();
  });

  it('should create', () => {
    expect(component).toBeTruthy();
  });
  
  // Utilizando el artefacto fixture
  it('should have a <input> tag with a placeholder: Search for...', () => {
    const searchEl = fixture.nativeElement;
    const inputEl = searchEl.querySelector('input');
    expect(inputEl).toBeTruthy();
    expect(inputEl.placeholder).toEqual('Search for...');
    
    // Agnóstico a la plataforma
    const searchDebug = fixture.debugElement
    const inputDebug = searchDebug.query(By.css('input'));
    const inputEl = inputDebug.nativeElement;
    expect(inputEl).toBeTruthy();
    expect(inputEl.placeholder).toEqual('Search for...');
  });
  
});
```

Es muy similar al  `.spec` al de los servicios pero tiene ciertas diferencias.

Con `ComponentFixture` tenemos un ambiente que nos Provee Angular para probar el componente, es la manera en que es renderizado y por tanto creado, de ese modo podemos utilizarlo. Con ayuda del artefacto `fixture` Angular creará el componente para obtener todos sus métodos.

Al igual que con las pruebas de servicios también necesitamos un módulo pequeño, para preparar nuestras pruebas usando `TestBed.configureTestingModule` (aunque de manera asíncrona).

Si queremos que la prueba a los elementos sea agnóstica a la plataforma debemos utilizar `fixture.debugElement`.

Cuando corremos en el navegador la aplicación de manera normal, Agular detecta los cambios en automático, sin embargo, en modo pruebas debemos utilizar `fixture.detectChanges();` cada que realizamos un cambio dentro del componente, por ejemplo, la detección de un `@Input()`.


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://shimozurdo.gitbook.io/frontend/testing/angular-testing.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
