@Input компонента модульного тестирования Angular. Обнаружение изменения значения

У меня есть простой сценарий, в котором (угловой 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);
  });

и это не работает :)

Используете ли вы стратегию OnPush? Если да, то это известная проблема (stackoverflow.com/questions/59883314/… ), попробуйте запустить cdr компонента (проверьте ответ с помощью runOnPushChangeDetection в stackoverflow.com/questions/59883314/…)

Sergei 22.03.2024 23:15

Возможно, вы ищете метод ComponentRef#setInput.

Shlang 22.03.2024 23:48

Честно говоря, я проверил эту стратегию, поскольку работал с ReactJS, а навыки Angukar немного заржавели.

Vladyn 23.03.2024 08:20
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Angular и React для вашего проекта веб-разработки?
Angular и React для вашего проекта веб-разработки?
Когда дело доходит до веб-разработки, выбор правильного front-end фреймворка имеет решающее значение. Angular и React - два самых популярных...
Эпизод 23/17: Twitter Space о будущем Angular, Tiny Conf
Эпизод 23/17: Twitter Space о будущем Angular, Tiny Conf
Мы провели Twitter Space, обсудив несколько проблем, связанных с последними дополнениями в Angular. Также прошла Angular Tiny Conf с 25 докладами.
Угловой продивер
Угловой продивер
Оригинал этой статьи на турецком языке. ChatGPT используется только для перевода на английский язык.
Мое недавнее углубление в Angular
Мое недавнее углубление в Angular
Недавно я провел некоторое время, изучая фреймворк Angular, и я хотел поделиться своим опытом со всеми вами. Как человек, который любит глубоко...
Освоение Observables и Subjects в Rxjs:
Освоение Observables и Subjects в Rxjs:
Давайте начнем с основ и постепенно перейдем к более продвинутым концепциям в RxJS в Angular
1
3
58
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Используя компонент тестового хоста (это предлагаемая мной и 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 лучше всего решить с помощью тестового хоста, как показано выше. Другие предложения меняют способ работы компонента, что делает тесты недействительными.

Wesley Trantham 23.03.2024 01:15

Но, как упомянул @segey, производительность могла бы быть лучше.

Vladyn 23.03.2024 08:30

Извините, но я, вероятно, пропустил - мой компонент является автономным и имеет значение true. Если оставлю как есть - получаю ошибку в тестах.

Vladyn 24.03.2024 18:46

Кроме того, мне это нужно, поскольку компонент фактически встраивает экземпляр Videogular. У меня есть триггер onChanges, и он вызывает метод init(). В моем случае мне нужен способ изменить input_title с (на, возможно, внутри, если это возможно) и, например, шпионить за методом init. Изменив ваш пример (который работает) на автономный: true, я вступаю в сложный сценарий.

Vladyn 24.03.2024 18:54

Не стесняйтесь задавать новые вопросы и отмечать меня. Я немного рассмотрел проект Videogular и думаю, что вам также захочется создать макет компонента, представляющий Videogular и то, что вы в него передаете. Я фанат тестирования взаимодействия с любым дочерним компонентом, отображаемым из html.

Wesley Trantham 24.03.2024 19:27

Понятно, но это что-то более серьезное с точки зрения тестирования. Я хотел только протестировать вход дочернего компонента, действительно содержащего Videogular. Изменяя этот ввод, я хочу протестировать метод точно так же, как в вашем примере, но компонент сделан как отдельный.

Vladyn 25.03.2024 13:32

Другие вопросы по теме