2

I am writing unit test for one of my Angular application which consists of multiple components. In one of my component (Component A), it has a child component like below

Component 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>



If you see the above HTML in the ComponentA i am having the component which calls the functions when the column is resized in the Grid which would call the onColumnResized() inside my ComponentA like below

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

My question is how do i spy the onColumnResized() or can i call the function directly like below


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

I Would like to know whether this is the correct approach as well as can i use spyOn() instead of calling the function directly

2
  • @Naren to spyOn(columnApi.getColumnState) do we need to call the onColumnResized()? Commented Sep 18, 2020 at 18:16
  • Check out my answer and let me know if it helped : Commented Sep 20, 2020 at 11:56

2 Answers 2

2

Nidhin, before I provide some answer let me explain the intention of unit testing for a component.

Unit testing of a component means that you should check the behavior (which includes HTML & ts code) of a component. One important factor which should be kept in mind is that "we should isolate the component(which is being tested) from external dependencies (mostly services) as much as possible"

IMHO, you should only check if the behavior is working when onColumnResized() is called. You should not be worried whether app-data-grid is calling it or not on (columnResized) . In app-data-grid tests, you should test whether columnResized is raised/invoked as per the expectation.

Note: You should test the behavior of both components working together as a part of Integration testing, which means that you test the working of several components together (integrated)

In your case, for below code:

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

you should do below tests:

  1. Make store inside the constructor as public (so that we can put spy on it by accessing it outside the component)
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 })
    );
  });
});

OR you can simply override the return type without creating a 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 )
    );
  });
});

You can refer this article of mine where I have combined few topics as a part of tutorial of Unit testing in Angular. I hope it helpes.

Sign up to request clarification or add additional context in comments.

2 Comments

Hi, I tried to follow the approach what you have said above but when i run the spec i am getting the error as cannot read length of undefined.I have checked the column resize() there is no logic to check the length. I have created another question in which i have mentioned the functions which are got called in ngOnInit(). Could you please help me whether the approach i have done over there is correct or not. Once that is fixed i hope the approach which you have given would work stackoverflow.com/questions/63990416/…
@Nidhinkumar : Feel free to clap on medium if it helped. :) Cheers mate !
2

You should not test if the method is called. You should test if the component behaves correct when the child component is resized.

There is a good library to mock your components: https://www.npmjs.com/package/ng-mocks

Something like that:

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

This way your tests don't rely on the internal implementation of your component.

You need to know if a method named onColumnResized is called. You have to ensure, that your store is updated with the correct columns when a resize happens...

2 Comments

I am new to testing could you tell me how to mock the columnApi
For mocking, please read: angular.io/guide/testing-services#angular-testbed Something like: jasmine.createSpyObj('ColumnApiMock', ['columnResized']);

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.