#angular #unit-testing #mocking #angular-content-projection
#angular #модульное тестирование #издевательство #angular-content-projection
Вопрос:
Я хочу протестировать компонент Angular и заменить contentChild на макет. Я следовал этому руководству: https://medium.com/angular-in-depth/angular-unit-testing-viewchild-4525e0c7b756 Руководство предназначено для дочернего элемента View, но я подумал, что это может сработать и для дочернего элемента content.
У моего contentChild есть наблюдаемый объект, на который подписывается родительский элемент. Если я протестирую код с реальным дочерним элементом, он работает. Если я издеваюсь над дочерним элементом, тест не работает. Я думаю, что дочерний макет, который я запрашиваю в тесте, чтобы выдать новое значение, является экземпляром, отличным от того, на который подписан родитель.
Мой родитель:
@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();
});
});
Ответ №1:
Вы можете использовать библиотеку тестирования с имитирующими функциями, такими как ng-mocks
Он обеспечивает MockBuilder
и MockComponent
.
Также он MockInstance
позволяет вам моделировать методы / свойства компонентов перед их инициализацией.
Я думаю, что ваш случай похож на то, как протестировать ViewChild / ViewChildren объявления
Ваш тест может выглядеть так:
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();
});
});
Комментарии:
1. Это может сработать, но в нашем проекте мы хотим установить как можно меньше пакетов, и мы считаем, что издевательство должно быть возможно без нового пакета.
2. аналогично, веб-приложения возможны без react и angular 🙂
Ответ №2:
Проблема заключается в том, что вы получаете с view-child неправильный экземпляр. Решение состоит в том, чтобы получить экземпляр из дочернего свойства родительского компонента.
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();
});
});