Componente hijo de prueba de unidad angular

Sep 19 2020

Estoy escribiendo una prueba unitaria para una de mis aplicaciones Angular que consta de varios componentes. En uno de mis componentes (Componente A), tiene un componente secundario como el siguiente

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>



Si ve el HTML anterior en el ComponentA, tengo el componente que llama a las funciones cuando se cambia el tamaño de la columna en la cuadrícula que llamaría a onColumnResized () dentro de mi ComponentA como a continuación

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

Mi pregunta es cómo puedo espiar el onColumnResized () o puedo llamar a la función directamente como se muestra a continuación


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

Me gustaría saber si este es el enfoque correcto y si puedo usar spyOn () en lugar de llamar a la función directamente

Respuestas

1 ShashankVivek Sep 20 2020 at 18:52

Nidhin, antes de dar una respuesta, permítame explicar la intención de las pruebas unitarias para un componente.

La prueba unitaria de un componente significa que debe verificar el comportamiento (que incluye HTML y tscódigo) de un componente. Un factor importante que debe tenerse en cuenta es que "debemos aislar el componente (que se está probando) de las dependencias externas (principalmente servicios) tanto como sea posible".

En mi humilde opinión, solo debe verificar si el comportamiento está funcionando cuando onColumnResized()se llama. No debe preocuparse si lo app-data-gridestá llamando o no (columnResized). En las app-data-gridpruebas, debe probar si columnResizedse genera / invoca según la expectativa.

Nota: debe probar el comportamiento de ambos componentes trabajando juntos como parte de la prueba de integración , lo que significa que prueba el funcionamiento de varios componentes juntos ( integrados )

En su caso, para el siguiente código:

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

debe hacer las siguientes pruebas:

  1. Hacer storedentro del constructorcomo público (para que podamos espiarlo accediendo fuera 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 })
    );
  });
});

O simplemente puede anular el tipo de retorno sin crear 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 )
    );
  });
});

Puede consultar este artículo mío donde he combinado algunos temas como parte del tutorial de pruebas unitarias en Angular. Espero que te ayude.

1 Flavio Sep 19 2020 at 02:41

No debe probar si se llama al método. Debe probar si el componente se comporta correctamente cuando se cambia el tamaño del componente secundario.

Hay una buena biblioteca para burlarse de sus componentes: https://www.npmjs.com/package/ng-mocks

Algo como eso:

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(...);
});

De esta manera, sus pruebas no dependen de la implementación interna de su componente.

Necesita saber si onColumnResizedse llama a un método llamado. Debe asegurarse de que su tienda se actualice con las columnas correctas cuando se produzca un cambio de tamaño ...