【问题标题】:Mocking Child Components - Angular 2模拟子组件 - Angular 2
【发布时间】:2020-11-07 15:47:28
【问题描述】:

在测试时如何模拟子组件?我有一个名为product-selected 的父组件,其模板如下所示:

<section id="selected-container" class="container-fluid">
    <hr/>
  <product-settings></product-settings>
  <product-editor></product-editor>
  <product-options></product-options>
</section>

组件声明如下所示:

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

import { ProductSettingsComponent } from '../settings/product-settings.component';                                      
import { ProductEditorComponent }   from '../editor/product-editor.component';                                      
import { ProductOptionsComponent }  from '../options/product-options.component';                                        

@Component({
    selector: 'product-selected',
    templateUrl: './product-selected.component.html',
    styleUrls: ['./product-selected.component.scss']
})
export class ProductSelectedComponent {}

这个组件实际上只是其他组件的一个地方,可能不会包含任何其他功能。

但是当我设置测试时,我收到以下模板错误,对所有三个组件重复:

Error: Template parse errors:
    'product-editor' is not a known element:
    1. If 'product-editor' is an Angular component, then verify that it is part of this module.
    2. If 'product-editor' is a Web Component then add "CUSTOM_ELEMENTS_SCHEMA" to the '@NgModule.schemas' of this component to suppress this message. ("
        <hr/>
      <product-settings></product-settings>
      [ERROR ->]<product-editor></product-editor>

我尝试加载子组件的模拟版本,但不知道该怎么做 - 我看到的示例只是覆盖了父组件,甚至没有提及子组件。那我该怎么做呢?

【问题讨论】:

    标签: angular


    【解决方案1】:

    小心NO_ERRORS_SCHEMA。让我们引用同一文档的另一部分:

    使用 NO_ERRORS_SCHEMA 的浅层组件测试极大地简化了复杂模板的单元测试。但是,编译器不再提醒您注意拼写错误或滥用组件和指令等错误。

    我发现这个缺点与编写测试的目的完全相反。更重要的是,模拟一个基本组件并不难。

    这里还没有提到的一种方法是在配置时简单地声明它们:

    @Component({
      selector: 'product-settings',
      template: '<p>Mock Product Settings Component</p>'
    })
    class MockProductSettingsComponent {}
    
    @Component({
      selector: 'product-editor',
      template: '<p>Mock Product Editor Component</p>'
    })
    class MockProductEditorComponent {}
    
    ...  // third one
    
    beforeEach(() => {
      TestBed.configureTestingModule({
          declarations: [
            ProductSelectedComponent,
            MockProductSettingsComponent,
            MockProductEditorComponent,
            // ... third one
          ],
          providers: [/* your providers */]
      });
      // ... carry on
    });
    

    【讨论】:

    • 我正在使用类似的东西来测试带有输入和输出的子组件,它非常适合验证绑定是否双向工作,但是,它容易发生位腐烂,作为“真实”子组件中的输入属性不会导致测试失败。我想知道是否可以通过以某种方式检查其注释来基于真实的子组件生成动态 Mock 类型。由于每个子组件都可以有一组未知的依赖注入,它必须是仅基于类型的东西,而不是子组件的实例化版本
    • 简而言之,我认为在这种情况下您需要做的就是对子组件进行单元测试。文档有一个part dedicated to testing inputs and outputs,希望您可以在其中找到所需的内容。如果我理解正确,您可能也会喜欢the part that directly follows
    • 好吧,这个例子是相反的。如何测试与其父级绑定的组件。如果您有一个包含 4 个子组件的父组件,并且您想单独测试父组件,这将无济于事,因为您仍然需要手动解析并提供所有子组件依赖项,或者手动维护 Mock 版本每个子组件。理想情况下,我可以将一个组件分成两部分,一个具有输入和输出的基本指令,以及一个提供模板的继承版本组件,但这不起作用,因为它们必须具有相同的选择器。
    • 谢谢你。一直在寻找,每个人都建议使用 NO_SCHEMA_ERRORS ......这对我来说似乎很hacky。至少在这里我们知道发生了什么
    • @Tanzeel 很好的发现!我打算邀请你在这里发布你自己的答案,但我看到 Soraz 已经建议了这个库。之前没注意。
    【解决方案2】:

    找到了一个近乎完美的解决方案,如果有人重构组件,它也会正确抛出错误:https://www.npmjs.com/package/ng-mocks

    npm install ng-mocks --save-dev
    

    现在你可以在你的 .spec.ts 中做

    import { MockComponent } from 'ng-mocks';
    import { ChildComponent } from './child.component.ts';
    // ...
    beforeEach(() => {
      TestBed.configureTestingModule({
        imports: [FormsModule, ReactiveFormsModule, RouterTestingModule],
        declarations: [
          ComponentUnderTest,
          MockComponent(ChildComponent), // <- here is the thing
          // ...
        ],
      });
    });
    

    这将创建一个新的匿名组件,它具有与 ChildComponent 相同的选择器、@Input()@Output() 属性,但没有附加代码。

    假设您的ChildComponent 有一个@Input() childValue: number,它绑定在您的被测组件中,&lt;app-child-component [childValue]="inputValue" /&gt;

    虽然它已被模拟,但您可以在测试中使用By.directive(ChildComponent),也可以像这样使用By.css('app-child-component')

    it('sets the right value on the child component', ()=> {
      component.inputValue=5;
      fixture.detectChanges();
      const element = fixture.debugElement.query(By.directive(ChildComponent));
      expect(element).toBeTruthy();
    
      const child: ChildComponent = element.componentInstance;
      expect(child.childValue).toBe(5);
    });
    

    【讨论】:

      【解决方案3】:

      一般来说,如果您在正在测试的组件的视图中使用了一个组件,并且您不一定要声明这些组件,因为它们可能有自己的依赖关系,以避免 "某些东西不是已知元素” 错误,您应该使用 NO_ERRORS_SCHEMA

      import { NO_ERRORS_SCHEMA }          from '@angular/core';
      
      TestBed.configureTestingModule({
              declarations: declarations,
              providers: providers
              schemas:      [ NO_ERRORS_SCHEMA ]
        })
      

      根据文档:

      将 NO_ERRORS_SCHEMA 添加到测试模块的架构元数据中,以告诉编译器忽略无法识别的元素和属性。您不再需要声明不相关的组件和指令。

      更多信息:https://angular.io/docs/ts/latest/guide/testing.html#!#shallow-component-test

      【讨论】:

      • 你知道我在那个测试页面上查看测试 Observables 并且从未注意到那部分。谢谢 - 这是一本很好的书,虽然我希望我早点找到它!接受你的答案,因为它比我的简单得多
      • 这是一个非常糟糕的解决方案,因为它掩盖了其他潜在的问题,因此与测试代码的目的背道而驰。 Arnaud P 给出了很好的答案。
      • 编写模拟组件可能会花费您更多的时间,但最终它们都是值得的。以后使用 NO_ERRORS_SCHEMA 会困扰你
      【解决方案4】:

      我发布了这个问题,以便在我为此苦苦挣扎一两天时发布答案。以下是你的做法:

      let declarations = [
        ProductSelectedComponent,
        ProductSettingsComponent,
        ProductEditorComponent,
        ProductOptionsComponent
      ];
      
      beforeEach(() => {
              TestBed.configureTestingModule({
                  declarations: declarations,
                  providers: providers
              })
              .overrideComponent(ProductSettingsComponent, {
                  set: {
                      selector: 'product-settings',
                      template: `<h6>Product Settings</h6>`
                  }
              })
              .overrideComponent(ProductEditorComponent, {
                  set: {
                      selector: 'product-editor',
                      template: `<h6>Product Editor</h6>`
                  }
              })
              .overrideComponent(ProductOptionsComponent, {
                  set: {
                      selector: 'product-options',
                      template: `<h6>Product Options</h6>`
                  }
              });
      
              fixture = TestBed.createComponent(ProductSelectedComponent);
              cmp = fixture.componentInstance;
              de = fixture.debugElement.query(By.css('section'));
              el = de.nativeElement;
          });
      

      您必须为每个子组件链接overrideComponent 函数。

      【讨论】:

      • 我认为第一个错误不是通过覆盖来解决的,你只需要声明那些你已经做过的组件,所以覆盖是没有意义的,除非你真的需要它
      • @Milad 确实如此,但在我的情况下,我没有包括在内,是这些组件对服务有自己的依赖关系,然后需要为它们包括在内。因为我只关心测试这个组件,而不关心超出非常基本的创建级别的其他组件,所以我可以很高兴地覆盖它们
      • 这真的会覆盖整个组件还是只是覆盖元数据。我使用了 overrideComponent 但我仍然有一个孩子抱怨缺少服务(仅用于那个孩子)。
      • 这是一个完美的答案,它使单元测试真的只是单独测试那个单元。
      猜你喜欢
      • 1970-01-01
      • 2016-11-23
      • 2017-02-16
      • 1970-01-01
      • 2017-03-03
      • 2016-04-29
      • 1970-01-01
      • 2018-09-28
      • 1970-01-01
      相关资源
      最近更新 更多