Angular 测试错误 -- 无法绑定到 'items',因为它不是 'app-dropdown' 的已知 属性

Angular Test error -- Can't bind to 'items' since it isn't a known property of 'app-dropdown'

只想说,我知道有很多与 "Can't bind to X since it isn't a known property of Y" 错误相关的 SO 帖子。我看了很多,找到了一些解决具体问题的答案,但我很难将其转化为我的案例,我实际上认为这很普遍,并且与对我应该如何解决我的用例。

我正在创建一个 Angular (7) 应用程序,我将其分为 componentsroutescomponents 是模块化的部分(下拉菜单、模态框、按钮等等),而路由是应用程序中的各个页面。这个术语有点复杂,因为两者在技术上都是 Angular 组件。换句话说,结构(在 src/ 内)如下所示:

- app
  - components
    - dropdown
      - dropdown.component.ts
      - dropdown.component.html
      - dropdown.component.scss
      - dropdown.component.spec.ts
  - routes
    - library
      - library.component.ts
      - library.component.html
      - library.component.scss
      - library.component.spec.ts
  ...

所以我有一个 Library 路由,它只是一个 Angular 组件,它导入一个 Dropdown 组件,看起来像这样:

import { Component, OnInit } from '@angular/core';

import { DropdownComponent } from '../../components/dropdown/dropdown.component';

@Component({
  selector: 'app-library',
  templateUrl: './library.component.html',
  styleUrls: ['./library.component.scss']
})
export class LibraryComponent implements OnInit {
  pickItem($event) {
    console.log($event.item, $event.index);
  }

  constructor() { }

  ngOnInit() {}
}

相关库HTML文件:

<div class="library row py4">
  <h3 class="typ--geo-bold typ--caps mb5">Style Library</h3>

  <div class="mb4" style="max-width: 35rem;">
    <p class="typ--geo-bold typ--caps">Dropdowns</p>
    <app-dropdown
      [items]="['One', 'Two', 'Three']"
      (pick)="pickItem($event)"
      title="Default dropdown"
    ></app-dropdown>
    <br />
    <app-dropdown
      [items]="['One', 'Two', 'Three']"
      (pick)="pickItem($event)"
      title="Inline dropdown"
      class="dropdown--inline"
    ></app-dropdown>
  </div>
</div>

下拉组件是一个基本组件,遵循类似的结构。除非有人问,否则我不会把它贴在这里,因为我不确定它是否会是附加的。 (只要说它 确实 接受 items 作为输入就够了——与以下错误相关)。

这在浏览器中完美运行,并在生产环境中正确构建。

当我 运行 我的 library.components.spec.ts 测试时,我 运行 出现以下错误:

Failed: Template parse errors:
Can't bind to 'items' since it isn't a known property of 'app-dropdown'.
1. If 'app-dropdown' is an Angular component and it has 'items' input, then verify that it is part of this module.
2. If 'app-dropdown' is a Web Component then add 'CUSTOM_ELEMENTS_SCHEMA' to the '@NgModule.schemas' of this component to suppress this message.
3. To allow any property add 'NO_ERRORS_SCHEMA' to the '@NgModule.schemas' of this component. ("
    <p class="typ--geo-bold typ--caps">Dropdowns</p>
    <app-dropdown
      [ERROR ->][items]="['One', 'Two', 'Three']"
      (pick)="pickItem($event)"
      title="Default dropdown"

这是基本的库规范文件:

import { TestBed, async } from '@angular/core/testing';
import { RouterTestingModule } from '@angular/router/testing';
import { LibraryComponent } from './library.component';

describe('LibraryComponent', () => {
  beforeEach(async(() => {
    TestBed.configureTestingModule({
      imports: [RouterTestingModule],
      declarations: [LibraryComponent],
    }).compileComponents();
  }));

  it('should create the component', () => {
    const fixture = TestBed.createComponent(LibraryComponent);
    const lib = fixture.debugElement.componentInstance;
    expect(lib).toBeTruthy();
  });
});

Library 组件和 Dropdown 组件都没有关联的模块。我的理解是这个错误可能与我没有将Dropdown组件导入Library模块之类的有关,但是

有没有办法在不将这些组件转换为模块的情况下使测试工作?所有路由组件都应该是模块吗?

编辑

添加下拉组件(如果相关):

<div
  class="dropdown"
  [ngClass]="{ open: open }"
  tab-index="-1"
  [class]="class"
  #dropdown
>
  <div class="dropdown__toggle" (click)="onTitleClick(dropdown)">
    <span class="dropdown__title">{{ finalTitle() }}</span>
    <span class="dropdown__icon icon-arrow-down"></span>
  </div>
  <div *ngIf="open" class="dropdown__menu">
    <ul>
      <li
        *ngFor="let item of items; let ind = index"
        class="dropdown__item"
        [ngClass]="{ selected: selectedIndex === ind }"
        (click)="onItemClick(dropdown, item, ind)"
      >
        <span class="dropdown__label">{{ item }}</span>
      </li>
    </ul>
  </div>
</div>

并且:

import { Component, OnInit, Input, Output, EventEmitter } from '@angular/core';

@Component({
  selector: 'app-dropdown',
  templateUrl: './dropdown.component.html',
  styleUrls: ['./dropdown.component.scss']
})
export class DropdownComponent implements OnInit {

  @Input() items: Array<string>;
  @Input() public selectedIndex: number = null;
  @Input() public title = 'Select one';
  @Input() public open = false;
  @Input() public class = '';

  @Output() pick = new EventEmitter();

  constructor() { }

  ngOnInit() {}

  finalTitle () {
    return typeof this.selectedIndex === 'number'
      ? this.items[this.selectedIndex]
      : this.title;
  }

  onItemClick(dropdown, item, index) {
    this._blur(dropdown);
    this.selectedIndex = index;
    this.open = false;
    this.pick.emit({ item, index });
  }

  onTitleClick(dropdown) {
    this._blur(dropdown);
    this.open = !this.open;
  }

  _blur(dropdown) {
    if (dropdown && dropdown.blur) { dropdown.blur(); }
  }
}

您需要在声明部分的测试模块上导入 DropdowmComponent

已更新以更清楚地回答您的 3 个问题

A) 有很多方法可以在不创建新模块的情况下实现这一点。您可以简单地将 DropdownComponent 导入到您的测试模块中,正如安德烈在下面所建议的那样。我在下面的这个答案中概述了另一种方法,它存根 DropdownComponent,只是为了测试 LibraryComponent。希望这能回答 "how to do that".

的问题

B) 我建议的存根不是一个模块——它几乎不是一个组件。没有必要将存根合并到整个应用程序中,它的唯一目的是测试 LibraryComponent。

C) 这个存根的价值只是为了测试 LibraryComponent,这就是为什么保持它尽可能简单是个好主意。

这是一种无需将组件转换为模块即可使测试正常运行的方法。

存根下拉组件:

下面是一种对您尝试在 LibraryComponent 中使用的 DropdownComponent 进行存根的方法。您在问题中详细说明的错误与您没有定义 'app-dropdown' 选择器这一事实直接相关,但您的 LibraryComponent 正试图绑定到它。假设您只想测试 library.component.spec.ts 文件中的 LibraryComponent,我建议保留 DropdownComponent 功能,而不是将实际组件导入测试。我创建了一个 Stackblitz 来说明我的意思。

来自 Stackblitz,这是来自 library.component.spec.ts 文件的片段:

@Component({
    selector: 'app-dropdown',
    template: `<h5>Dropdown</h5>`
})
class TestDropdownComponent {
    @Input() items;
    @Input() title;
    @Output() pick = new EventEmitter<any>();
}

describe('LibraryComponent', () => {
    let component: LibraryComponent;
    let fixture: ComponentFixture<LibraryComponent>;

    beforeEach(async(() => {
        TestBed.configureTestingModule({
            imports: [ RouterTestingModule ],
            declarations: [ 
                TestDropdownComponent,
                LibraryComponent
            ]
        }).compileComponents();
        fixture = TestBed.createComponent(LibraryComponent);
    }));
    it('should create the component', () => {
      const fixture = TestBed.createComponent(LibraryComponent);
      const lib = fixture.debugElement.componentInstance;
      expect(lib).toBeTruthy();
    });
});

Note - If you want to reproduce the error you detail in your question within the Stackblitz, simply comment out the line for TestDropdownComponent in the declarations of the TestBed so it looks like so:

TestBed.configureTestingModule({
    imports: [ RouterTestingModule ],
    declarations: [ 
        // TestDropdownComponent,
        LibraryComponent
    ]
}).compileComponents();

And you will once again be testing just your component without a mock/stub of DropdownComponent to bind to.