Componente filho de teste de unidade angular

Sep 19 2020

Estou escrevendo um teste de unidade para um dos meus aplicativos Angular, que consiste em vários componentes. Em um dos meus componentes (Componente A), ele tem um componente filho como abaixo

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 você vir o HTML acima no Componente A, estou tendo o componente que chama as funções quando a coluna é redimensionada na grade que chamaria onColumnResized () dentro do meu ComponentA como abaixo

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

Minha pergunta é como faço para espionar o onColumnResized () ou posso chamar a função diretamente como abaixo


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

Gostaria de saber se esta é a abordagem correta e também se posso usar spyOn () em vez de chamar a função diretamente

Respostas

1 ShashankVivek Sep 20 2020 at 18:52

Nidhin, antes de responder, deixe-me explicar a intenção do teste de unidade para um componente.

O teste de unidade de um componente significa que você deve verificar o comportamento (que inclui HTML e tscódigo) de um componente. Um fator importante que deve ser mantido em mente é que "devemos isolar o componente (que está sendo testado) das dependências externas (principalmente serviços) o máximo possível"

IMHO, você só deve verificar se o comportamento está funcionando quando onColumnResized()é chamado. Você não deve se preocupar se app-data-gridestá ligando ou não (columnResized). Em app-data-gridtestes, você deve testar se columnResizedé gerado / invocado de acordo com a expectativa.

Nota: Você deve testar o comportamento de ambos os componentes trabalhando juntos como parte do teste de integração , o que significa que você testa o funcionamento de vários componentes juntos ( integrados )

No seu caso, para o código abaixo:

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

você deve fazer os testes abaixo:

  1. Tornar storedentro do constructorcomo público (para que possamos espioná-lo acessando-o fora do 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 })
    );
  });
});

OU você pode simplesmente substituir o tipo de retorno sem criar um 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 )
    );
  });
});

Você pode consultar este meu artigo, onde combinei alguns tópicos como parte do tutorial de testes de unidade em Angular. Espero que ajude.

1 Flavio Sep 19 2020 at 02:41

Você não deve testar se o método é chamado. Você deve testar se o componente se comporta corretamente quando o componente filho é redimensionado.

Há uma boa biblioteca para simular seus componentes: https://www.npmjs.com/package/ng-mocks

Algo parecido:

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

Dessa forma, seus testes não dependem da implementação interna de seu componente.

Você precisa saber se um método chamado onColumnResizedé chamado. Você tem que garantir que sua loja seja atualizada com as colunas corretas quando um redimensionamento acontecer ...