Custom Component Rendering Test

Discussão em 'Angular' iniciado por Hugo Leonardo, Janeiro 15, 2025.

  1. I have an Angular 15 application and I created a component that is basically the same as an Angular Material snackbar, however, I am having problems when creating unit tests with Jasmine, as it always reports a rendering error, as if there were a missing dependency or they were in the wrong rendering order, I did tests by creating several forms of mocks, but without success.

    Below is the service file

    import { ApplicationRef, ComponentRef, createComponent, Injectable } from "@angular/core";
    import { SnackbarComponent } from "../../pages/common/snackbar/snackbar.component";
    import { HttpErrorResponse } from "@angular/common/http";

    @Injectable({
    providedIn: 'root'
    })

    export class SnackbarService {
    private snackbarRef: ComponentRef<SnackbarComponent> | null = null;
    private timeoutId: any;

    constructor(
    private appRef: ApplicationRef,
    ) { }


    showSnackbar(
    type: 'success' | 'error' | 'warning' | 'info',
    message: string | HttpErrorResponse,
    options: {
    vertical?: 'top' | 'bottom';
    horizontal?: 'left' | 'center' | 'right';
    button?: true | false
    } = {
    vertical: 'top',
    horizontal: 'right',
    },

    duration: number = 10000 // dez segundos por enquanto
    ) {

    if (this.snackbarRef) {
    this.closeSnackbar()
    }

    this.snackbarRef = createComponent(SnackbarComponent, {
    environmentInjector: this.appRef.injector
    })

    this.snackbarRef.instance.config = { type, message, options }
    this.appRef.attachView(this.snackbarRef.hostView)
    document.body.appendChild(this.snackbarRef.location.nativeElement);
    this.timeoutId = setTimeout(() => this.closeSnackbar(), duration);
    }

    closeSnackbar() {
    if (this.snackbarRef) {
    this.appRef.detachView(this.snackbarRef.hostView)
    this.snackbarRef.destroy()
    this.snackbarRef = null

    if(this.timeoutId){
    clearTimeout(this.timeoutId);
    this.timeoutId = null
    }
    }
    }

    }


    Here is the service test file

    import { CommonModule } from '@angular/common';
    import { HttpClientTestingModule } from '@angular/common/http/testing';
    import { fakeAsync, TestBed, tick } from '@angular/core/testing';
    import { ApplicationRef } from '@angular/core';
    import { SnackbarService } from './snackbar.service';
    import { SnackbarComponent, SnackbarConfig } from '../../pages/common/snackbar/snackbar.component';
    import { HttpErrorResponse } from '@angular/common/http';


    describe('SnackbarsnackbarService ', () => {

    let snackbarService: SnackbarService;
    let appRefSpy: jasmine.SpyObj<ApplicationRef>;



    beforeEach(() => {
    appRefSpy = jasmine.createSpyObj('ApplicationRef', ['attachView', 'detachView', 'injector']);

    TestBed.configureTestingModule({
    declarations: [SnackbarComponent],
    imports: [HttpClientTestingModule, CommonModule],
    providers: [SnackbarService, { provide: ApplicationRef, useValue: appRefSpy } ],
    }).compileComponents();
    snackbarService = TestBed.inject(SnackbarService);
    appRefSpy = TestBed.inject(ApplicationRef) as jasmine.SpyObj<ApplicationRef>;
    });

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

    it('should show a snackbar and attach it to the view', () => {
    const message = 'Test message';
    snackbarService.showSnackbar('success', message);

    expect(appRefSpy.attachView).toHaveBeenCalled();
    expect(document.body.querySelector('.snackbar')).toBeTruthy();
    });

    it('should apply the correct configuration to the snackbar', () => {
    const config: SnackbarConfig = {
    type: 'error',
    message: 'An error occurred',
    options: { vertical: 'bottom', horizontal: 'center' },
    };

    snackbarService.showSnackbar(config.type, config.message, config.options);

    const snackbarElement = document.body.querySelector('.snackbar');
    expect(snackbarElement).toBeTruthy();
    expect(snackbarElement?.classList.contains('error')).toBeTrue();
    expect(snackbarElement?.classList.contains('bottom')).toBeTrue();
    expect(snackbarElement?.classList.contains('center-horizontal')).toBeTrue();
    });

    it('should close the snackbar and remove it from the view', () => {
    snackbarService.showSnackbar('info', 'Test info message');
    snackbarService.closeSnackbar();

    expect(appRefSpy.detachView).toHaveBeenCalled();
    expect(document.body.querySelector('.snackbar')).toBeNull();
    });

    it('should clear timeout when the snackbar is closed manually', () => {
    spyOn(window, 'clearTimeout');
    snackbarService.showSnackbar('info', 'Test info message');
    snackbarService.closeSnackbar();

    expect(clearTimeout).toHaveBeenCalled();
    });

    it('should close the snackbar automatically after the specified duration', fakeAsync(() => {
    const duration = 10000;
    snackbarService.showSnackbar('info', 'Test auto-close message', {}, duration);
    tick(duration)
    expect(document.body.querySelector('.snackbar')).toBeNull();
    }));

    it('should handle HttpErrorResponse messages', () => {
    const errorResponse = new HttpErrorResponse({ status: 500, statusText: 'Server Error' });
    spyOn(snackbarService, 'closeSnackbar').and.callThrough();

    snackbarService.showSnackbar('error', errorResponse);

    expect(appRefSpy.attachView).toHaveBeenCalled();
    expect(snackbarService.closeSnackbar).not.toHaveBeenCalled();
    });


    });


    The only test that passes is the 'should be created'

    And this error persists:

    Angular was not able to inject a renderer (RendererFactory2). Likely this is due to a broken DI hierarchy. Make sure that any injector used to create this component has a correct parent

    Continue reading...

Compartilhe esta Página