Jasmine

Featured image

Holisss cracksssss como estan? Hoy les traigo una pequeña dosis de #codeflix! Estos días no encontraba mucha inspiración (?) y ahora digamos que… tampoco

enter image description here

Bueno el post de hoy va dedicado a Jasmine. Unit testing es un tema que no siempre gusta asi que por eso mismo quise traer algunos conceptos y ejercicios para que subir esas ganas de levantar esa coverageeeee de tu componente o servicio!

enter image description here

Test configuration

Bien, cuando queremos arrancar a armar el test suit de un componente existente, lo primero que hacemos es crear el archivo spec, con exactamente el mismo nombre de nuestro componente en cuestión, ejemplo: home.component.ts —> home.component.spec.ts.

Nota: el tema del nombre es meramente por convención y organización.

En ese archivo, ponemos la base del test suit algo asi:

    describe('HomeComponent Test', () => {

    let  component: HomeComponent ;
    let  fixture: ComponentFixture<HomeComponent>;

    beforeEach(() =>  TestBed.configureTestingModule({
        declarations: [HomeComponent]
        imports: [],
        providers: []
    }));

    beforeEach(() => {
        fixture = TestBed.createComponent(HomeQuoteFormComponent);
        component = fixture.componentInstance;
    });

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

Sobre esta estructura base vamos a trabajar y vamos a tratar de verificar que nuestro componente realmente se crea. Para poder hacer que esta base funcione, tenemos que configurar un poco el configureTestingModule, que funciona exactamente igual a la inyección de dependencias que realizamos con el @NgModule de Angular.

En este caso vamos a limitarnos a importar lo necesario para que el componente se cree, asi como tambien en providers los servicios que éste necesita para su implementación y que podemos encontrar en el constructor del componente existente que queremos testear. En declarations la idea es dejar solo el componente que queremos testear, para favorecer el isolation y enforcarnos a testear únicamente nuestro componente, sin meternos en sub componentes o componentes padres. Para poder evitar que nuestro test runner (Karma) nos exija que tenemos componentes propios padres o hijos que se relacionan con nuestro HomeComponent, existe una property que podemos usar llamada schemas:

Y la usamos así:

    schemas: [CUSTOM_ELEMENTS_SCHEMA],

Con eso le decimos que nuestro componente tendrá relacion con otros, pero que no queremos importar todas esas dependencias porque sólo vamos a testear nuestro componente, realizando unit testing y no functional testing o tests de integración.

Lo más común también, es que en providers nos pida el RouterTestingModule, HttpClientTestingModule, no siempre son necesarios pero si nos lo pide cuando ejecutamos ng test lo deberemos aplicar.

Hay distintas formas de mockear los servicios, que es la parte quizás mas interesante y desafiante a la hora de mockear la configuración de nuestro componente para poder empezar a testearlo.

Jasmine Spies

enter image description here Un spy básicamente nos sirve para NO llamar al método real con sus argumentos, sino que nos mockea la función que le pedimos haciendo de cuenta que se llamó, y omite toda la implementación que la función tenga incluso su retorno.

Para trabajar con spies, sólo necesitamos primero en provider agregar el/los servicios que el componente necesita en su constructor:

    providers: [HomeService]

y luego en el beforeEach lo agregamos, nos quedaría:

        beforeEach(() => {
            fixture = TestBed.createComponent(HomeQuoteFormComponent);
            component = fixture.componentInstance;
            homeService = TestBed.get(HomeService);
        });

En nuestro HomeService seguramente tendremos nuestros metodos y variables de clase propios del servicio que hayamos creado, pero para que el componente home se cree, sólo necesitamos mockear, en primera instancia, los metodos/properties que estemos usando en el ciclo ngOnInit() de nuestro componente. Con un Spy nos quedaría:

    getContentSpy = spyOn(homeService, 'getContent');

y listo, si nosotros estamos haciendo un this.homeService.getContent(); en nuestro onInit, el spy anterior funcionará mockeando ese método para que el componente pueda crearse.

Test Doubles

enter image description here Otra forma de mockear o simular como trabajan nuestros servicios que son dependencia de nuestro componente, es manejarlos como Stubs.

Qué significa esto? Que podemos tener una clase que simule ser nuestro servicio, tendrá exactamente los mismos métodos (o al menos sólo los necesarios que use nuestro componente) y variables de clase de ser necesario, con lo que nosotros deseemos que retorne.

Para aplicar este método solo tenemos que crear un archivo y exportar esta clase, por ejemplo home.service.stub.ts y que contenga:

    export class HomeServiceStub {
        getContent(): void {
           return 'holis';
        }
    }

Este archivo lo importamos en nuestro spec y en providers, vamos a decirle a nuestra configuración que en lugar de usar el servicio real que tiene el provider del componente, que use el nuestro que creamos recién, cómo se lo decimooosss?

        providers: [
    	    { provide:  HomeService, useClass: HomeServiceStub },
        ]

y listooooooooo enter image description here

Conclusión

En todo el ejemplo anterior pudimos ver, cómo crear y generar un spec de cero, para un componente existente determinado, su configuración base que cubra la inicialización del mismo, y un ejemplo de dependencia con un servicio específico, el cual hace una llamada a un método getContent() dentro del ciclo OnInit.

También vimos de dos formas diferentes como mockeamos el método de ese servicio, para que nuestro componente pueda inicializarse y crearse correctamente.

¿Cuál es la diferencia?

Si lo hacemos con la primera opción del Spy, tendremos posibilidades de acceder a los métodos que nos ofrece Jasmine para testear, por ejemplo si queremos testear si se llamó, podremos hacerlo simplemente así:

    expect(homeServiceSpy).toHaveBeenCalled();

y a cualquier otro método que querramos usar de Jasmine.

De la segunda manera no podemos trackear ni usar esos métodos, pero si nos sirve para mockear el servicio entero o parte del mismo.

Ninguna opción es la mejor ni la más adecuada, simplemente depende de lo que necesitemos testear y lo que abarque nuestra cobertura, para algunas ocasiones vamos a querer si o si testear y trackear las llamadas por lo que usaremos spies, y en otros momentos solo necesitaremos mockear y enfocar nuestro testing en otros aspectos.

En el próximo post voy a detallar distintos spies y otras formas de stubbear, para no extender la lectura de hoy :)

enter image description here