Angular 测试模拟 ContentChild
Angular Test Mock ContentChild
我想测试一个 Angular 组件并用 Mock 替换内容Child。我遵循了这个指南:https://medium.com/angular-in-depth/angular-unit-testing-viewchild-4525e0c7b756
该指南适用于视图Child,但我认为这也适用于内容Child。
我的内容Child 有一个 parent 订阅的 Observable。如果我用真实的 child 测试代码,它就可以工作。如果我模拟 child,测试将不起作用。我认为 child 模拟,我在测试中查询以发出一个新值,是一个不同的实例,而不是 parent 订阅的实例。
我的parent:
@Component({
// tslint:disable-next-line:component-selector
selector: '[appParent]',
template: `
<div>
<span class="true" *ngIf="display$ | async; else other">True</span>
</div>
<ng-content></ng-content>
<ng-template #other><span class="false">False</span></ng-template>
`
})
export class ParentComponent implements AfterContentInit {
@ContentChild(ChildComponent) child!: ChildComponent;
display$: Observable<boolean>;
ngAfterContentInit(): void {
this.display$ = this.child.display$;
}
}
我的模拟:
@Component({
selector: 'app-child',
template: '',
providers: [
{
provide: ChildComponent,
useExisting: ChildStubComponent
}
]
})
export class ChildStubComponent {
displaySubject = new BehaviorSubject(false);
display$: Observable<boolean> = this.displaySubject.asObservable();
}
测试:
describe('ParentComponentTest', () => {
@Component({
template: `
<div appParent>
<app-child></app-child>
</div>
`
})
class TestComponent {
@ViewChild(ChildStubComponent)
child!: ChildStubComponent;
}
let component: TestComponent;
let fixture: ComponentFixture<TestComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [
TestComponent,
ChildStubComponent,
ParentComponent
]
}).compileComponents();
});
beforeEach(async () => {
fixture = TestBed.createComponent(TestComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should find true', () => {
component.child.displaySubject.next(true);
fixture.detectChanges();
expect(fixture.debugElement.query(By.css('.true'))).toBeTruthy();
});
});
您可以使用具有模拟功能的测试库,例如 ng-mocks
提供MockBuilder
and MockComponent
.
它还有 MockInstance
允许您在初始化之前模拟组件方法/属性。
我认为你的情况类似于How to test a ViewChild / ViewChildren of a declaration
您的测试可能如下所示:
describe('ParentComponentTest', () => {
// keeping ParentComponent as it is and mocking ChildStubComponent
beforeEach(() => MockBuilder(ParentComponent).mock(ChildStubComponent));
it('should find true', () => {
// providing a mock upfront
const displaySubject = MockInstance(
ChildStubComponent,
'displaySubject',
new Subject(),
);
// rendering desired template
const fixture = MockRender(`
<div appParent>
<app-child></app-child>
</div>
`);
// testing behavior
displaySubject.next(true);
fixture.detectChanges();
expect(ngMocks.find('.true')).toBeTruthy();
});
});
问题似乎是,您获得的视图子实例不是正确的实例。解决方法是从父组件的子组件属性中获取实例
describe('ParentComponentTest', () => {
@Component({
template: `
<div appParent>
<app-child></app-child>
</div>
`
})
class TestComponent {
@ViewChild(ParentComponent)
parent!: ParentComponent;
}
let component: TestComponent;
let fixture: ComponentFixture<TestComponent>;
....
it('should find true', () => {
const childStub = component.parent.child.toArray()[0] as unknown as ChildStubComponent;
childStub.displaySubject.next(true);
fixture.detectChanges();
expect(fixture.debugElement.query(By.css('.true'))).toBeTruthy();
});
});
我想测试一个 Angular 组件并用 Mock 替换内容Child。我遵循了这个指南:https://medium.com/angular-in-depth/angular-unit-testing-viewchild-4525e0c7b756 该指南适用于视图Child,但我认为这也适用于内容Child。
我的内容Child 有一个 parent 订阅的 Observable。如果我用真实的 child 测试代码,它就可以工作。如果我模拟 child,测试将不起作用。我认为 child 模拟,我在测试中查询以发出一个新值,是一个不同的实例,而不是 parent 订阅的实例。
我的parent:
@Component({
// tslint:disable-next-line:component-selector
selector: '[appParent]',
template: `
<div>
<span class="true" *ngIf="display$ | async; else other">True</span>
</div>
<ng-content></ng-content>
<ng-template #other><span class="false">False</span></ng-template>
`
})
export class ParentComponent implements AfterContentInit {
@ContentChild(ChildComponent) child!: ChildComponent;
display$: Observable<boolean>;
ngAfterContentInit(): void {
this.display$ = this.child.display$;
}
}
我的模拟:
@Component({
selector: 'app-child',
template: '',
providers: [
{
provide: ChildComponent,
useExisting: ChildStubComponent
}
]
})
export class ChildStubComponent {
displaySubject = new BehaviorSubject(false);
display$: Observable<boolean> = this.displaySubject.asObservable();
}
测试:
describe('ParentComponentTest', () => {
@Component({
template: `
<div appParent>
<app-child></app-child>
</div>
`
})
class TestComponent {
@ViewChild(ChildStubComponent)
child!: ChildStubComponent;
}
let component: TestComponent;
let fixture: ComponentFixture<TestComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [
TestComponent,
ChildStubComponent,
ParentComponent
]
}).compileComponents();
});
beforeEach(async () => {
fixture = TestBed.createComponent(TestComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should find true', () => {
component.child.displaySubject.next(true);
fixture.detectChanges();
expect(fixture.debugElement.query(By.css('.true'))).toBeTruthy();
});
});
您可以使用具有模拟功能的测试库,例如 ng-mocks
提供MockBuilder
and MockComponent
.
它还有 MockInstance
允许您在初始化之前模拟组件方法/属性。
我认为你的情况类似于How to test a ViewChild / ViewChildren of a declaration
您的测试可能如下所示:
describe('ParentComponentTest', () => {
// keeping ParentComponent as it is and mocking ChildStubComponent
beforeEach(() => MockBuilder(ParentComponent).mock(ChildStubComponent));
it('should find true', () => {
// providing a mock upfront
const displaySubject = MockInstance(
ChildStubComponent,
'displaySubject',
new Subject(),
);
// rendering desired template
const fixture = MockRender(`
<div appParent>
<app-child></app-child>
</div>
`);
// testing behavior
displaySubject.next(true);
fixture.detectChanges();
expect(ngMocks.find('.true')).toBeTruthy();
});
});
问题似乎是,您获得的视图子实例不是正确的实例。解决方法是从父组件的子组件属性中获取实例
describe('ParentComponentTest', () => {
@Component({
template: `
<div appParent>
<app-child></app-child>
</div>
`
})
class TestComponent {
@ViewChild(ParentComponent)
parent!: ParentComponent;
}
let component: TestComponent;
let fixture: ComponentFixture<TestComponent>;
....
it('should find true', () => {
const childStub = component.parent.child.toArray()[0] as unknown as ChildStubComponent;
childStub.displaySubject.next(true);
fixture.detectChanges();
expect(fixture.debugElement.query(By.css('.true'))).toBeTruthy();
});
});