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

});