我在Angular中有一个测试套件,我试图在其中测试具有多个子组件的组件。我需要断言在调用父方法时会调用子方法。
我正在实现的类如下:
export class UserFiltersComponent implements OnInit, OnDestroy {
@Output() filtersChange: EventEmitter<any> = new EventEmitter();
@ViewChild('tooltip', {static: false}) applyTooltip: MatTooltip;
@ViewChild('filterSearch', {static: false}) searchComponent: SearchComponent;
@ViewChild('filterTitle', {static: false}) titleComponent: TitleComponent;
@ViewChild('filterSkills', {static: false}) skillsComponent: SkillsComponent;
@ViewChild('filterEnglish', {static: false}) englishComponent: EnglishLevelComponent;
@ViewChild('filterLocation', {static: false}) locationComponent: LocationComponent;
@ViewChild('filterEducation', {static: false}) educationComponent: EducationComponent;
@ViewChild('filterWork', {static: false}) workComponent: WorkComponent;
@ViewChild('filterSocial', {static: false}) socialProfileComponent: SocialProfileComponent;
@ViewChild('filterRegistered', {static: false}) registeredComponent: RegisteredComponent;
@ViewChild('filterInvitation', {static: false}) invitationComponent: InvitationsComponent;
.
.
.
populateFilters(result: any): void {
const filter = {
id: result.id,
name: result.name,
values: result.value
};
this.filters = filter;
this.searchComponent.populate(filter.values.name);
this.titleComponent.populate(filter.values.titles);
this.skillsComponent.populate(filter.values.skills);
this.englishComponent.populate(filter.values.englishLevel);
this.locationComponent.populate(filter.values.locations);
this.educationComponent.populate(filter.values.educations);
this.workComponent.populate(filter.values.works);
this.socialProfileComponent.populate(filter.values.profiles);
this.registeredComponent.populate(filter.values.registeredExact, filter.values.registeredGte, filter.values.registeredLte);
this.invitationComponent.populate(filter.values.invitationsExact, filter.values.invitationsLte, filter.values.invitationsGte);
}
我为此代码编写的测试是:
import {SearchComponent} from '@feature/administration/user/user-filters/search';
import {TitleComponent} from '@feature/administration/user/user-filters/title';
import {SkillsComponent} from '@feature/administration/user/user-filters/skills';
import {EnglishLevelComponent} from '@feature/administration/user/user-filters/english-level';
import {LocationComponent} from '@feature/administration/user/user-filters/location';
import {WorkComponent} from '@feature/administration/user/user-filters/work';
import {EducationComponent} from '@feature/administration/user/user-filters/education';
import {SocialProfileComponent} from '@feature/administration/user/user-filters/social-profile';
import {RegisteredComponent} from '@feature/administration/user/user-filters/registered';
import {InvitationsComponent} from '@feature/administration/user/user-filters/invitations';
.
.
.
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [
BrowserAnimationsModule,
MatTooltipModule,
TranslateTestingModule
],
declarations: [
UserFiltersComponent,
SearchComponent
],
providers: [
{
provide: UserFiltersService,
useClass: UserFiltersServiceStub
},
{
provide: PageLoadingService,
useClass: PageLoadingServiceStub
},
{
provide: AuthenticationService,
useClass: AuthenticationServiceStub
},
{
provide: UserService,
useClass: UserServiceStub
},
{
provide: MatDialog,
useClass: MatDialogStub
},
],
schemas: [
NO_ERRORS_SCHEMA
]
})
.compileComponents();
}));
.
.
.
it('should populate the filters', () => {
const filter = {
id: '12345',
name: 'filters test',
value: {
name: 'search',
titles: [''],
skills: [''],
englishLevel: 1,
locations: [''],
educations: [''],
works: [''],
profiles: [''],
registeredExact: null,
registeredGte: null,
registeredLte: null,
invitationsExact: null,
invitationsLte: null,
invitationsGte: null
}
};
spyOn(component.searchComponent, 'populate');
component.populateFilters(filter);
expect(component.searchComponent.populate).toHaveBeenCalled();
});
到目前为止,一切正常。问题是当我尝试添加其余的子组件时:
declarations: [
UserFiltersComponent,
SearchComponent,
TitleComponent
],
.
.
.
it('should populate the filters', () => {
const filter = {
id: '12345',
name: 'filters test',
value: {
name: 'search',
titles: [''],
skills: [''],
englishLevel: 1,
locations: [''],
educations: [''],
works: [''],
profiles: [''],
registeredExact: null,
registeredGte: null,
registeredLte: null,
invitationsExact: null,
invitationsLte: null,
invitationsGte: null
}
};
spyOn(component.searchComponent, 'populate');
spyOn(component.titleComponent, 'populate');
component.populateFilters(filter);
expect(component.searchComponent.populate).toHaveBeenCalled();
expect(component.titleComponent.populate).toHaveBeenCalled();
});
然后我遇到以下错误:
Summary of all failing tests
FAIL src/app/feature/administration/user/user-filters/user-filters.component.spec.ts (10.102s)
● UserFiltersComponent › should create
NullInjectorError: StaticInjectorError(DynamicTestModule)[TitleComponent -> FormBuilder]:
StaticInjectorError(Platform: core)[TitleComponent -> FormBuilder]:
NullInjectorError: No provider for FormBuilder!
at NullInjector.get (../packages/core/src/di/injector.ts:44:21)
at resolveToken (../packages/core/src/di/injector.ts:337:20)
at tryResolveToken (../packages/core/src/di/injector.ts:279:12)
at StaticInjector.get (../packages/core/src/di/injector.ts:168:14)
at resolveToken (../packages/core/src/di/injector.ts:337:20)
at tryResolveToken (../packages/core/src/di/injector.ts:279:12)
at StaticInjector.get (../packages/core/src/di/injector.ts:168:14)
at resolveNgModuleDep (../packages/core/src/view/ng_module.ts:125:25)
at NgModuleRef_.get (../packages/core/src/view/refs.ts:507:12)
at resolveDep (../packages/core/src/view/provider.ts:423:43)
at createClass (../packages/core/src/view/provider.ts:277:11)
at createDirectiveInstance (../packages/core/src/view/provider.ts:136:20)
at createViewNodes (../packages/core/src/view/view.ts:303:28)
at callViewAction (../packages/core/src/view/view.ts:636:7)
at execComponentViewsAction (../packages/core/src/view/view.ts:559:7)
at createViewNodes (../packages/core/src/view/view.ts:331:3)
at createRootView (../packages/core/src/view/view.ts:210:3)
at callWithDebugContext (../packages/core/src/view/services.ts:630:23)
at Object.debugCreateRootView [as createRootView] (../packages/core/src/view/services.ts:122:10)
at ComponentFactory_.create (../packages/core/src/view/refs.ts:93:27)
at initComponent (../../packages/core/testing/src/test_bed.ts:589:28)
at ZoneDelegate.Object.<anonymous>.ZoneDelegate.invoke (node_modules/zone.js/dist/zone.js:391:26)
at ProxyZoneSpec.Object.<anonymous>.ProxyZoneSpec.onInvoke (node_modules/zone.js/dist/proxy.js:129:39)
at ZoneDelegate.Object.<anonymous>.ZoneDelegate.invoke (node_modules/zone.js/dist/zone.js:390:52)
at Object.onInvoke (../packages/core/src/zone/ng_zone.ts:273:25)
at ZoneDelegate.Object.<anonymous>.ZoneDelegate.invoke (node_modules/zone.js/dist/zone.js:390:52)
at Zone.Object.<anonymous>.Zone.run (node_modules/zone.js/dist/zone.js:150:43)
at NgZone.run (../packages/core/src/zone/ng_zone.ts:171:50)
at TestBedViewEngine.createComponent (../../packages/core/testing/src/test_bed.ts:593:56)
at Function.TestBedViewEngine.createComponent (../../packages/core/testing/src/test_bed.ts:232:36)
at beforeEach (src/app/feature/administration/user/user-filters/user-filters.component.spec.ts:131:23)
at ZoneDelegate.Object.<anonymous>.ZoneDelegate.invoke (node_modules/zone.js/dist/zone.js:391:26)
at ProxyZoneSpec.Object.<anonymous>.ProxyZoneSpec.onInvoke (node_modules/zone.js/dist/proxy.js:129:39)
at ZoneDelegate.Object.<anonymous>.ZoneDelegate.invoke (node_modules/zone.js/dist/zone.js:390:52)
at Zone.Object.<anonymous>.Zone.run (node_modules/zone.js/dist/zone.js:150:43)
at Object.testBody.length (node_modules/jest-preset-angular/zone-patch/index.js:52:27)
我意识到构造器正在尝试构建FormBuilder时出现了我做错的事情,但这并未被嘲笑。我的观点是,我不需要嘲笑这一点。我只需要断言调用了“填充”方法,因为在每个组件中都创建了测试以测试填充方法。我该如何模拟这个子组件以断言该方法已被调用?
答案 0 :(得分:2)
恕我直言...如果要对方法进行单元测试,请尝试将该方法与外部依赖项隔离。这样,您可以解决复杂性(例如,不必要的喷油器初始化)和潜在的错误。在这种情况下,模拟是我的首选。因此,在这种情况下,我建议以这种方式提出单独的关注点:
首先,模拟类:
class TitleComponentStub {
populate = () => {
};
}
然后将提供程序添加到TitleComponent
,
{
provide: TitleComponent,
useClass: TitleComponentStub
},
在测试内部添加一行:
it('should populate the filters', () => {
component.titleComponent = TestBed.get(TitleComponent); // <- THIS LINE
const filter = {
id: '12345',
name: 'filters test',
[...]
从此处删除TitleComponent
:
declarations: [
UserFiltersComponent,
SearchComponent,
TitleComponent
],
之后,如果要测试titleComponent.populate
,则可以为其创建单独的单元测试:)。
答案 1 :(得分:0)
我通常为子组件创建组件存根。那只包含我需要的输入,然后调用我想查看的所有功能。
我将像下面这样的代码转储到我的spec文件的底部(如果打算再次使用它,则将其转储到共享位置),并将'MockTitleComponent'添加到我的声明中。从那里,您应该可以照常进行间谍活动和期望。
@Component({
selector: 'app-title-component',
template: '<p>Mock App Title Component</p>'
})
class MockTitleComponent{
@Input()
Input1;
@Input()
Input2;
testFunction(){}
}
答案 2 :(得分:0)
谢谢Kyle Anderson。不幸的是,它不能解决我的问题。
感谢Walter Gómez Milán评论,我设法解决了问题。当我添加
component.titleComponent = TestBed.get(TitleComponent);
它修复了它。
最终解决方案如下:
import {AuthenticationService, PageLoadingService, UserFiltersService, UserService} from '@core/services';
import {UserLogin} from '@core/models';
import {UserFiltersComponent} from './user-filters.component';
import {TranslateTestingModule} from 'src/app/test-utils';
import {SearchComponent} from '@feature/administration/user/user-filters/search';
import {TitleComponent} from '@feature/administration/user/user-filters/title';
import {SkillsComponent} from '@feature/administration/user/user-filters/skills';
import {EnglishLevelComponent} from '@feature/administration/user/user-filters/english-level';
import {LocationComponent} from '@feature/administration/user/user-filters/location';
import {WorkComponent} from '@feature/administration/user/user-filters/work';
import {EducationComponent} from '@feature/administration/user/user-filters/education';
import {SocialProfileComponent} from '@feature/administration/user/user-filters/social-profile';
import {RegisteredComponent} from '@feature/administration/user/user-filters/registered';
import {InvitationsComponent} from '@feature/administration/user/user-filters/invitations';
.
.
.
class FilterComponentStub {
populate = () => {
}
}
.
.
.
beforeEach(async(() => {
.
.
.providers: [
{
provide: UserFiltersService,
useClass: UserFiltersServiceStub
},
{
provide: PageLoadingService,
useClass: PageLoadingServiceStub
},
{
provide: AuthenticationService,
useClass: AuthenticationServiceStub
},
{
provide: UserService,
useClass: UserServiceStub
},
{
provide: MatDialog,
useClass: MatDialogStub
},
{
provide: SearchComponent,
useClass: FilterComponentStub
},
{
provide: TitleComponent,
useClass: FilterComponentStub
},
{
provide: SkillsComponent,
useClass: FilterComponentStub
},
{
provide: EnglishLevelComponent,
useClass: FilterComponentStub
},
{
provide: LocationComponent,
useClass: FilterComponentStub
},
{
provide: WorkComponent,
useClass: FilterComponentStub
},
{
provide: EducationComponent,
useClass: FilterComponentStub
},
{
provide: SocialProfileComponent,
useClass: FilterComponentStub
},
{
provide: RegisteredComponent,
useClass: FilterComponentStub
},
{
provide: InvitationsComponent,
useClass: FilterComponentStub
},
],
.
.
.
}
.
.
.
it('should populate the filters', () => {
const filter = {
id: '12345',
name: 'filters test',
value: {
name: 'search',
titles: [''],
skills: [''],
englishLevel: 1,
locations: [''],
educations: [''],
works: [''],
profiles: [''],
registeredExact: null,
registeredGte: null,
registeredLte: null,
invitationsExact: null,
invitationsLte: null,
invitationsGte: null
}
};
component.searchComponent = TestBed.get(SearchComponent);
spyOn(component.searchComponent, 'populate');
component.titleComponent = TestBed.get(TitleComponent);
spyOn(component.titleComponent, 'populate');
component.skillsComponent = TestBed.get(SkillsComponent);
spyOn(component.skillsComponent, 'populate');
component.englishComponent = TestBed.get(EnglishLevelComponent);
spyOn(component.englishComponent, 'populate');
component.locationComponent = TestBed.get(LocationComponent);
spyOn(component.locationComponent, 'populate');
component.educationComponent = TestBed.get(EducationComponent);
spyOn(component.educationComponent, 'populate');
component.workComponent = TestBed.get(WorkComponent);
spyOn(component.workComponent, 'populate');
component.socialProfileComponent = TestBed.get(SocialProfileComponent);
spyOn(component.socialProfileComponent, 'populate');
component.registeredComponent = TestBed.get(RegisteredComponent);
spyOn(component.registeredComponent, 'populate');
component.invitationComponent = TestBed.get(InvitationsComponent);
spyOn(component.invitationComponent, 'populate');
component.populateFilters(filter);
expect(component.searchComponent.populate).toHaveBeenCalled();
expect(component.titleComponent.populate).toHaveBeenCalled();
expect(component.skillsComponent.populate).toHaveBeenCalled();
expect(component.englishComponent.populate).toHaveBeenCalled();
expect(component.locationComponent.populate).toHaveBeenCalled();
expect(component.educationComponent.populate).toHaveBeenCalled();
expect(component.workComponent.populate).toHaveBeenCalled();
expect(component.socialProfileComponent.populate).toHaveBeenCalled();
expect(component.registeredComponent.populate).toHaveBeenCalled();
expect(component.invitationComponent.populate).toHaveBeenCalled();
});
然后所有测试都在运行。