Componente figlio di unit test angolare

Sep 19 2020

Sto scrivendo un test unitario per una delle mie applicazioni angolari che consiste di più componenti. In uno dei miei componenti (Componente A), ha un componente figlio come di seguito

Componente A

<div class="row row-top">
  <div class="col-12">
    <app-data-grid
      [columnDefs]="columnDefs"
      [defaultColDef]="defaultColumnDefs"
      [overlayNoRowsTemplate]="overlayNoRowsTemplate"
      [hasFloatingFilter]="hasFloatingFilter"
      [frameworkComponents]="frameworkComponents"
      [rowData]="rowData"
      [hasMultipleRows]="rowSelection"
      [hasRowAnimation]="hasRowAnimation"
      [multiSortKey]="multiSortKey"
      (rowDataChanged)="onRowDataChanged()"
      (selectionChanged)="onSelectionChanged()"
      (rowClicked)="gotoDetailView($event)" (sortChanged)="onSortChanged($event)"
      (columnResized)="onColumnResized()"
      (gridReady)="OnGridReady($event)"
    >
    </app-data-grid>

    <div class="float-right">
      <button
        id="addDevice"
        type="button"
        class="btn btn-brand btn-primary position-relative add-button"
        (click)="gotoAddDevice()"
      >
        <i class="fa fa-plus"></i>
      </button>
    </div>
  </div>
</div>



Se vedi l'HTML sopra nel ComponentA sto avendo il componente che chiama le funzioni quando la colonna viene ridimensionata nella griglia che chiamerebbe onColumnResized () all'interno del mio ComponentA come sotto

onColumnResized() {
    const updatedColumns: ColumnWidth[] = [];
    this.columnApi.getColumnState().forEach((column) => {
      updatedColumns.push({ field: column.colId, width: column.width });
    });
    this.store.dispatch(new DevicesActions.UpdateColumnWidth(updatedColumns));
  }

La mia domanda è come spio onColumnResized () o posso chiamare la funzione direttamente come sotto


    describe('ColumnResized', () => {
        it('call the onColumnResized', () => {
          expect(component.onColumnResized()).toHaveBeenCalled();
        });
      });

Vorrei sapere se questo è l'approccio corretto e posso usare spyOn () invece di chiamare direttamente la funzione

Risposte

1 ShashankVivek Sep 20 2020 at 18:52

Nidhin, prima di fornire qualche risposta lascia che ti spieghi l'intenzione di testare un componente.

Il test di unità di un componente significa che è necessario verificare il comportamento (che include HTML e tscodice) di un componente. Un fattore importante da tenere presente è che "dovremmo isolare il componente (che viene testato) dalle dipendenze esterne (principalmente servizi) il più possibile"

IMHO, dovresti controllare solo se il comportamento funziona quando onColumnResized()viene chiamato. Non dovresti preoccuparti se lo stai app-data-gridchiamando o meno (columnResized). Nei app-data-gridtest, dovresti verificare se columnResizedviene sollevato / invocato secondo le aspettative.

Nota: è necessario testare il comportamento di entrambi i componenti che lavorano insieme come parte del test di integrazione , il che significa che si verifica il funzionamento di diversi componenti insieme ( integrato )

Nel tuo caso, per il codice seguente:

onColumnResized() {
    const updatedColumns: ColumnWidth[] = [];
    this.columnApi.getColumnState().forEach((column) => {
      updatedColumns.push({ field: column.colId, width: column.width });
    });
    this.store.dispatch(new DevicesActions.UpdateColumnWidth(updatedColumns));
 }

dovresti fare i seguenti test:

  1. Rendi pubblico storel' constructoras (in modo da poterlo spiare accedendo al di fuori del componente)
export class MockColumnApi {
  getColumnState() {
    return [
      {colId: 1, column: 2 }
    ];
  }
}

describe('SomeComponent', () => {
  let component: SomeComponent;
  let fixture: ComponentFixture<SomeComponent>;
  beforeEach(async(() => {
    TestBed.configureTestingModule({
      imports: [
         // whatever you need to import
      ],
      declarations: [SomeComponent, GridComponent],
      providers: [
        { provide: ColumnApi, useClass: MockColumnApi },
      ],
    }).compileComponents();
  }));

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

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

  it('should create dispatch columns on resize call', () => {
    spyOn(component.columnApi, 'getColumnState').and.callThrough();
    spyOn(component.store, 'dispatch').and.callThrough();
    component.onColumnResized();
    expect(component.columnApi.getColumnState).toHaveBeenCalled()
    expect(component.store.dispatch).toHaveBeenCalledWith(
      new DevicesActions.UpdateColumnWidth({colId: 1, column: 2 })
    );
  });
});

OPPURE puoi semplicemente sovrascrivere il tipo restituito senza creare un MockColumnApi:

describe('SomeComponent', () => {
  let component: SomeComponent;
  let fixture: ComponentFixture<SomeComponent>;
  beforeEach(async(() => {
    TestBed.configureTestingModule({
      imports: [
         // whatever you need to import
      ],
      declarations: [SomeComponent, GridComponent],
      providers: [
        { provide: ColumnApi, useClass: MockColumnApi },
      ],
    }).compileComponents();
  }));

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

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

  it('should create dispatch columns on resize call', () => {
    const sampleData = [
      {colId: 1, column: 2 }
    ];
    spyOn(component.columnApi, 'getColumnState').and.returnValue(sampleData );
    spyOn(component.store, 'dispatch').and.callThrough();
    component.onColumnResized();
    expect(component.columnApi.getColumnState).toHaveBeenCalled()
    expect(component.store.dispatch).toHaveBeenCalledWith(
      new DevicesActions.UpdateColumnWidth(sampleData )
    );
  });
});

Puoi fare riferimento a questo mio articolo in cui ho combinato alcuni argomenti come parte del tutorial di Unit testing in Angular. Spero che aiuti.

1 Flavio Sep 19 2020 at 02:41

Non dovresti testare se il metodo viene chiamato. Dovresti verificare se il componente si comporta correttamente quando il componente figlio viene ridimensionato.

C'è una buona libreria per deridere i tuoi componenti: https://www.npmjs.com/package/ng-mocks

Qualcosa del genere:

let columnApiMock: jasmine.SpyObj<ColumnApi>;

beforeEach(() => {
  TestBed.configureTestingModule({
    ...
    providers: [
      { provide: YourStoreService, 
        useValue: jasmine.createSpyObj('YourStoreService', ['dispatch']) },
      { provide: ColumnApi, 
        useValue: jasmine.createSpyObj('ColumnApi', ['getColumnState']) },
    ],
    declarations: [
      AppComponentA, // not mocked
      MockComponent(AppDataGrid),
    ],
  });
  columnApiMock = TestBed.inject(ColumnApi) as jasmine.SpyObj<ColumnApi>;
})

...

it('test if columns are stored on resize', () => {
  // expect you have mocked your colum api, prepare columns that should be returned
  columnApiMock.getColumnState.and.returnValue([...]);
    
  const dataGridMock = ngMocks.find(fixture.debugElement, AppDataGrid);
  // trigger Output from DataGrid
  dataGridMock.columnResized.emit();

  expect(TestBed.inject(YourStoreService).dispatch))
    .toHaveBeenCalledWith(...);
});

In questo modo i tuoi test non si basano sull'implementazione interna del tuo componente.

Devi sapere se onColumnResizedviene chiamato un metodo denominato. Devi assicurarti che il tuo negozio sia aggiornato con le colonne corrette quando si verifica un ridimensionamento ...