У меня есть простой сценарий, в котором (угловой 17.3) я пытаюсь проверить, реагирует ли компонент при изменении входного свойства. У меня есть свойство, переданное из родительского компонента, где оно передается как @Input в дочернем компоненте. Что-то вроде того:
родитель
...
title = 'Hola!'
...
Разметка родителя:
...
<my-child-component [input_title] = "title" />
...
у ребенка:
....
@Input({ required: true }) input_title: String = '';
..
...
ngOnChanges(changes: SimpleChanges): void {
const simpleChange = changes['input_title'];
if (simpleChange.previousValue && (simpleChange.currentValue !== simpleChange.previousValue)) {
this.init(); // I WANT to test this method
}
}
...
По сути, я хочу протестировать этот метод и вызвать изменения. Более того, я хочу протестировать изменение в названии. Как я могу сделать это из моего модульного теста?
У меня есть что-то вроде:
let component: SimpleComponent;
let fixture: ComponentFixture<SimpleComponent>;
let compiled: HTMLElement;
beforeEach(() => {
fixture = TestBed.createComponent(SimpleComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
...
it('should change the title of the component', () => {
const playSpy = spyOn(component, 'init');
console.info(component?.input_title);
const newTitle= 'blablabla';
component.input_title= newTitle;
fixture.detectChanges();
compiled = fixture.nativeElement as HTMLElement;
console.info(compiled.querySelector('h1')?.textContent);
});
и это не работает :)
Возможно, вы ищете метод ComponentRef#setInput.
Честно говоря, я проверил эту стратегию, поскольку работал с ReactJS, а навыки Angukar немного заржавели.





Используя компонент тестового хоста (это предлагаемая мной и Angular стратегия), вы можете добиться этой цели; ДАЖЕ С OnPush. Я позволил себе вольность в отношении того, что может делать ваш метод init — короче говоря, вам нужно установить заголовок дважды, потому что именно так вы написали свои изменения.
Html для простого компонента
<div id = "initStatus">initialized: {{initialized}}</div>
ts-файл
import { Component, Input, OnChanges, SimpleChanges } from '@angular/core';
@Component({
selector: 'my-child-component',
templateUrl: './simple.component.html',
styleUrl: './simple.component.css'
})
export class SimpleComponent implements OnChanges {
initialized = false;
@Input({ required: true }) input_title: String = '';
ngOnChanges(changes: SimpleChanges): void {
const simpleChange = changes['input_title'];
if (simpleChange.previousValue && (simpleChange.currentValue !== simpleChange.previousValue)) {
this.init(); // I WANT to test this method
}
}
private init(): void {
this.initialized = true;
}
}
файл спецификации
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { SimpleComponent } from './simple.component';
import { Component } from '@angular/core';
import { By } from '@angular/platform-browser';
@Component(({
template: `<my-child-component [input_title] = "title"></my-child-component>`,
}))
class TestHostComponent {
title = 'first title';
}
describe('SimpleComponent', () => {
let component: TestHostComponent;
let fixture: ComponentFixture<TestHostComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [SimpleComponent, TestHostComponent]
})
.compileComponents();
fixture = TestBed.createComponent(TestHostComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should init', () => {
const debugElement = fixture.debugElement.query(By.css('#initStatus'));
expect(debugElement.nativeElement.textContent).toBe('initialized: false');
component.title = 'second title'; // here is what you are missing
fixture.detectChanges();
expect(debugElement.nativeElement.textContent).toBe('initialized: true');
});
});
Теперь давайте обсудим ngOnChanges. Он выполняется при КАЖДОМ ИЗМЕНЕНИИ, поэтому я полностью его избегаю, чтобы предотвратить проблемы с производительностью. Вот новый файл ts, который делает то же самое, но с установщиком и OnPush.
import { ChangeDetectionStrategy, Component, Input } from '@angular/core';
@Component({
selector: 'my-child-component',
templateUrl: './simple.component.html',
styleUrl: './simple.component.css',
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class SimpleComponent {
currentTitle: string
initialized = false;
@Input({ required: true }) set input_title(value: string) {
const changed = !!this.currentTitle;
this.currentTitle = value;
if (changed) {
this.init();
}
}
private init(): void {
this.initialized = true;
}
}
Написав это таким образом, становится немного более очевидным, что ваш init вызывается только тогда, когда вы меняете существующий заголовок.
Удачи и приятного кодирования!
@Сергей, упомянутую вами проблему OnPush лучше всего решить с помощью тестового хоста, как показано выше. Другие предложения меняют способ работы компонента, что делает тесты недействительными.
Но, как упомянул @segey, производительность могла бы быть лучше.
Извините, но я, вероятно, пропустил - мой компонент является автономным и имеет значение true. Если оставлю как есть - получаю ошибку в тестах.
Кроме того, мне это нужно, поскольку компонент фактически встраивает экземпляр Videogular. У меня есть триггер onChanges, и он вызывает метод init(). В моем случае мне нужен способ изменить input_title с (на, возможно, внутри, если это возможно) и, например, шпионить за методом init. Изменив ваш пример (который работает) на автономный: true, я вступаю в сложный сценарий.
Не стесняйтесь задавать новые вопросы и отмечать меня. Я немного рассмотрел проект Videogular и думаю, что вам также захочется создать макет компонента, представляющий Videogular и то, что вы в него передаете. Я фанат тестирования взаимодействия с любым дочерним компонентом, отображаемым из html.
Понятно, но это что-то более серьезное с точки зрения тестирования. Я хотел только протестировать вход дочернего компонента, действительно содержащего Videogular. Изменяя этот ввод, я хочу протестировать метод точно так же, как в вашем примере, но компонент сделан как отдельный.
Используете ли вы стратегию OnPush? Если да, то это известная проблема (stackoverflow.com/questions/59883314/… ), попробуйте запустить cdr компонента (проверьте ответ с помощью runOnPushChangeDetection в stackoverflow.com/questions/59883314/…)