No tienes acceso a esta clase

¡Continúa aprendiendo! Únete y comienza a potenciar tu carrera

Servicios con dependencias

8/25
Recursos

Aportes 7

Preguntas 1

Ordenar por:

¿Quieres ver más aportes, preguntas y respuestas de la comunidad?

Angular tiene un concepto importante conocido como inyección de dependencias. Es decir que podemos inyectar servicios, componentes, directivas o pipes a otros componentes, directivas, pipes, etc.

En estos casos cuando un servicio depende de otro o un componente depende de otro se debe realizar pruebas de la siguiente manera.

primero crearemos un servicio extra

ng g s services/master

y vamos a ingrersar el siguiente código

master.service.ts

import { Injectable } from '@angular/core';

import  { ValueService } from './value.service';

@Injectable({
  providedIn: 'root'
})

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

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

Como se puede observar masterService tiene una inyección de dependencia de valueService.

Para poder realizar las pruebas en valueService ingresar el siguiente código en el master.service.spec.ts

master.service.spec.ts

import { MasterService } from './master.service';
import { ValueService } from './value.service';

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');
  });
});

como masterService recibe como parametro en su contructor un servicio de valueService, para poder realizar las pruebas desde masterService es necesario crear ambas instancias y enviar en masterService a valueService como parametro.

Importante

En las pruebas unitarias, todo lo que venga como dependencias lo vamos a ejecutar de forma fake o mock.
Quiere decir que solo se va a verificar la funcionalidad exacta de esa prueba por ejemplo

Si getValue en valueService es probada, su test sera ver si devuelve el valor asignado en la variable privada value.

Mientras que si la función getValue es provada desde masterService, su test verificara si se esta llamando correctamente la función getValue desde ValueService.

Por lo que para probar se debera hacer mocks de todo lo que venga como dependencias. Por que lo único que se debe probar es su ejecución, no si funciona como tal

Mocks o servicios tipo fake

para observar lo que se hace en un mock vamos a crear un archivo dentro de services llamado ‘value-fake.service.ts’ y añadimos las funcionalidades de value.service

value-fake.service.ts

export class FakeValueService {

  constructor() { }

  getValue() {
    return 'fake value';
  }

  setValue (value: string) { }

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

}

Ahora lo importamos a nuestro master value y configuramos mediante un as unknow as ValueService para verificar que se ejecuta la prueba sin necesidad de observar si las funciones de value service se producen correctamente.

master.service.spec.ts

import { FakeValueService } from './value-fake.service';

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');
  });

Esta es la forma menos eficiente de hacer pruebas sobretodo de mantenimiento.

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

Por lo que, cuando master service lo ejecute probara un fake que se esta enviando en forma de objeto

master.service.spec.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.
Si al momento de hacer pruebas llamamos al API KEY de google maps se puede consumir tantas veces el servicio de manera consecutiva que

  1. consumiria mucho saldo por uso de la API KEY
  2. podría verse como un ataque de DNS y cancelar tus servicios.

Para evitar todo esto se enviar un fake y con eso evitamos el consumo inutil de servicios.

Existen métodos para no ejecutar un servicio de dependencia real sino uno fake, con el propósito de emular las respuestas, permitiéndonos centrarnos en el servicio a testear y no en las dependencias.

Fake Services: Hacer mocks/clones de los servicios dependencia (método poco mantenible)

Esto es un cascarón del servicio, retornando métodos con valores predefinidos, fakes.

it('sould 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');
  });

Usar objetos como clases:

Nos permite hacer una función dentro de un objeto en la misma prueba, emulando el método a testear.

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

Estas formas son manuales, hay maneras automatizadas de hacerlo con Spies

Servicios

💡 Lo servicios en Angular funcionan gracias al patrón de Inyección de dependencias, el cual nos permite inyectar nuestro servicios en diferentes lugares cómo componentes, directivas, etc.

Cómo desarrolladores, también nos encontraremos con el escenario dónde tengamos que probar componentes que depende de un servicio e incluso, servicios depende de otros servicios. Es ahí donde la inyección de dependencias puede ser una herramienta o un problema más (sino la podemos usar).

Algo a tener en cuenta, es que nuestra pruebas unitarias deben encargarse solamente de evaluar la responsabilidad que le corresponde a la función o clase que estamos evaluando.

💡 Es decir, a un conjunto de pruebas unitarias no le debe importar sí una dependencia funciona bien o no, ya que dicha dependencia debería tener su conjunto de pruebas unitarias que se encarguen de verificar su correcto funcionamiento.

Una forma de hacer pruebas unitarias de un servicio que depende de otros servicios es utilizar la técnica de inyección de dependencias. Esto implica proporcionar a la clase o función que se está probando una instancia de cada uno de los servicios que depende, en lugar de crearlos directamente en el código.

De esta manera, puedes proporcionar versiones “simuladas” o “mock” de los servicios dependientes durante las pruebas, lo que te permite controlar el comportamiento de los servicios dependientes y asegurar que tu código se está ejecutando correctamente.

Puedes crear mock services (u objects) para cada uno de los servicios que depende tu servicio y configurarlos para que devuelvan valores predefinidos cuando se llamen. Esto te permite probar tu servicio de manera aislada sin depender de los servicios reales, lo cual es realmente útil cuando quieres probar servicios que se conecta a una API (por citar un ejemplo), debido a que de esta manera, te evitas el consumo de recursos externos.

⚠️ Es fundamental asegurarte de que tus pruebas sean lo suficientemente exhaustivas para cubrir todos los casos de uso posibles y garantizar la calidad de tu código.

No he llegado a la mitad, y ya me exploto la cabeza.

Una buena forma de hacer los mocks es implementar los databuilders de cada servicio o clase

Increíble, recien lo entendi a ultimo la importancia de la clase.

Hubiera sido bueno que getValue() de master, hubiera tenido un nombre diferente a getValue de Value.service.

Así, no sería un trabalenguas