Angular 单元测试子组件
Angular unit test child component
我正在为我的 Angular 应用程序之一编写单元测试,该应用程序由多个组件组成。在我的一个组件(组件 A)中,它有一个如下所示的子组件
组件 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>
如果您在 ComponentA 中看到上面的 HTML 我有 组件,当在 Grid 中调整列大小时调用函数 onColumnResized () 在我的 ComponentA 中,如下所示
onColumnResized() {
const updatedColumns: ColumnWidth[] = [];
this.columnApi.getColumnState().forEach((column) => {
updatedColumns.push({ field: column.colId, width: column.width });
});
this.store.dispatch(new DevicesActions.UpdateColumnWidth(updatedColumns));
}
我的问题是如何监视 onColumnResized() 或者我可以像下面那样直接调用该函数
describe('ColumnResized', () => {
it('call the onColumnResized', () => {
expect(component.onColumnResized()).toHaveBeenCalled();
});
});
我想知道这是否是正确的方法,以及我是否可以使用 spyOn() 而不是直接调用该函数
您不应该测试该方法是否被调用。您应该测试在调整子组件大小时组件的行为是否正确。
有一个很好的库可以模拟您的组件:https://www.npmjs.com/package/ng-mocks
类似的东西:
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(...);
});
这样您的测试就不会依赖组件的内部实现。
您需要知道是否调用了名为 onColumnResized
的方法。您必须确保在调整大小时您的商店更新为正确的列...
Nidhin,在我提供一些答案之前,让我解释一下对组件进行单元测试的目的。
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"
恕我直言,您应该只在调用 onColumnResized()
时检查该行为是否有效。您不必担心 app-data-grid
是否在 (columnResized)
上调用它。在 app-data-grid
测试中,您应该测试 columnResized
是否符合预期 raised/invoked。
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)
在您的情况下,对于以下代码:
onColumnResized() {
const updatedColumns: ColumnWidth[] = [];
this.columnApi.getColumnState().forEach((column) => {
updatedColumns.push({ field: column.colId, width: column.width });
});
this.store.dispatch(new DevicesActions.UpdateColumnWidth(updatedColumns));
}
你应该做以下测试:
- 将
constructor
内部的 store
设为 public(以便我们可以通过在组件外部访问它来监视它)
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 })
);
});
});
或者您可以简单地覆盖 return 类型而不创建 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 )
);
});
});
您可以参考 this article of mine 我在 Angular 中结合了几个主题作为单元测试教程的一部分。希望对你有所帮助。
我正在为我的 Angular 应用程序之一编写单元测试,该应用程序由多个组件组成。在我的一个组件(组件 A)中,它有一个如下所示的子组件
组件 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>
如果您在 ComponentA 中看到上面的 HTML 我有 组件,当在 Grid 中调整列大小时调用函数 onColumnResized () 在我的 ComponentA 中,如下所示
onColumnResized() {
const updatedColumns: ColumnWidth[] = [];
this.columnApi.getColumnState().forEach((column) => {
updatedColumns.push({ field: column.colId, width: column.width });
});
this.store.dispatch(new DevicesActions.UpdateColumnWidth(updatedColumns));
}
我的问题是如何监视 onColumnResized() 或者我可以像下面那样直接调用该函数
describe('ColumnResized', () => {
it('call the onColumnResized', () => {
expect(component.onColumnResized()).toHaveBeenCalled();
});
});
我想知道这是否是正确的方法,以及我是否可以使用 spyOn() 而不是直接调用该函数
您不应该测试该方法是否被调用。您应该测试在调整子组件大小时组件的行为是否正确。
有一个很好的库可以模拟您的组件:https://www.npmjs.com/package/ng-mocks
类似的东西:
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(...);
});
这样您的测试就不会依赖组件的内部实现。
您需要知道是否调用了名为 onColumnResized
的方法。您必须确保在调整大小时您的商店更新为正确的列...
Nidhin,在我提供一些答案之前,让我解释一下对组件进行单元测试的目的。
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"
恕我直言,您应该只在调用 onColumnResized()
时检查该行为是否有效。您不必担心 app-data-grid
是否在 (columnResized)
上调用它。在 app-data-grid
测试中,您应该测试 columnResized
是否符合预期 raised/invoked。
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)
在您的情况下,对于以下代码:
onColumnResized() {
const updatedColumns: ColumnWidth[] = [];
this.columnApi.getColumnState().forEach((column) => {
updatedColumns.push({ field: column.colId, width: column.width });
});
this.store.dispatch(new DevicesActions.UpdateColumnWidth(updatedColumns));
}
你应该做以下测试:
- 将
constructor
内部的store
设为 public(以便我们可以通过在组件外部访问它来监视它)
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 })
);
});
});
或者您可以简单地覆盖 return 类型而不创建 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 )
);
});
});
您可以参考 this article of mine 我在 Angular 中结合了几个主题作为单元测试教程的一部分。希望对你有所帮助。