Макет содержимого Angular Test

#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();
  });

});