【问题标题】:Angular 9 Unit Test: how to mock import of tested component?Angular 9 单元测试:如何模拟测试组件的导入?
【发布时间】:2020-10-08 10:57:27
【问题描述】:

所以我目前正在为 Angular 9 组件编写 Jasmine/Karma 单元测试。

我的应用程序如何工作的简短摘要: 我用 D3 写了一点Funnel,它以类似漏斗的图表显示给定数据。然后我写了一个FunnelComponent,其中包含这个Funnel,并在实际图表旁边显示了一些元信息。

这是我要测试的组件:

funnel.component.ts

import { Component } from '@angular/core';
import { Funnel } from './d3charts/funnel.ts';
import { FunnelData } from './funnel.data';

@Component({
  selector: 'v7-funnel-component',
  templateUrl: './funnel.html'
})
export class FunnelComponent {

  private funnel: Funnel = null;

  constructor() {}

  public createFunnel(funnelData: FunnelData): void {
      this.funnel = new Funnel();
      this.funnel.setData(funnelData);
      this.funnel.draw();
  }
}

这是我对该组件的 karma-jasmine 单元测试:

funnel.component.spec.ts

import { TestBed, ComponentFixture } from '@angular/core/testing';
import { } from 'jasmine';
import { FunnelComponent } from './funnel.component';
import { Component } from '@angular/core';
import { FunnelData } from './funnel.data';
import { Funnel } from './d3charts/funnel.ts';

@Component({selector: 'funnel', template: ''})
class FunnelStub {
  private data: FunnelData = null;

  public setData(data: FunnelData): void
  {this.data = data;}
  {}
  public draw(): void
  {}
  public update(funnelData: FunnelData): void
  {}
}

/**
 * Testing class for FunnelComponent.
 */
describe('Component: Funnel', () => {

  let component: FunnelComponent;
  let fixture: ComponentFixture<FunnelComponent>;

  beforeEach(async(() => {
    TestBed
    .configureTestingModule({
      declarations: [
        FunnelComponent,
        FunnelStub
      ],
      providers: [
        { provide: Funnel, useValue: FunnelStub}
      ]
    })
    .compileComponents()
    .then(() => {
      fixture = TestBed.createComponent(FunnelComponent);
      component = fixture.componentInstance;
    });
  }));

it('#createFunnel should set data of funnel. Filled data should set filled funnel.', () => {
    expect(component["funnel"]).toBeNull();

    let exampleFunnelData = new FunnelData("testcaption", "testdescription", 8);
    component.createFunnel(exampleFunnelData);

    expect(component["funnel"]).toBeDefined();
    expect(component["funnel"]["data"]).toBeDefined();
    expect(component.data.caption).toEqual("testcaption");
    expect(component.data.description).toEqual("testsubtext");
    expect(component.data.value).toEqual(8);
  });
});

我想在这里测试createFunnel 方法。 但我不希望我的 createFunnel 方法将真正的Funnel 分配给this.funnel,而是使用我的FunnelStub。 知道怎么做吗?

{ provide: Funnel, useValue: FunnelStub} 添加到我的提供程序数组没有帮助:(

最好的问候, 塞巴斯蒂安

【问题讨论】:

    标签: angular unit-testing karma-jasmine


    【解决方案1】:

    这不起作用,因为漏斗不是提供者:

    providers: [
      { provide: Funnel, useValue: FunnelStub}
    ]
    

    对测试最有帮助的是创建一个用于创建漏斗的服务。

    export abstract class FunnelProvider {
      abstract createFunnel(): Funnel;
    }
    
    

    然后,此服务可以有 2 种实现,一种在您的应用中使用,另一种在您的测试中使用。

    @Injectable()
    export class DefaultFunnelProvider {
      createFunnel() {
        return new Funnel(); // Return real funnel object.
      }
    }
    
    @Injectable()
    export class MockFunnelProvider {
      createFunnel() {
         // Return stub funnel. Assuming that they share the same interface.
        return new FunnelStub() as Funnel;
      }
    }
    

    现在您必须在 FunnelModule(声明您的 FunnelComponent 的模块)中定义 DefaultFunnelProvider

    @NgModule({
      ... // other stuff
      providers: [{provide: FunnelProvider, useClass: DefaultFunnelProvider}]
    })
    export class FunnelModule {}
    
    

    然后你在你的 FunnelComponent 中注入 FunnelProvider 并使用它来创建漏斗:

    export class FunnelComponent {
    
      private funnel: Funnel = null;
    
      constructor(private funnelProvider: FunnelProvider) {}
    
      public createFunnel(funnelData: FunnelData): void {
          this.funnel = this.funnelProvider.createFunnel();
          this.funnel.setData(funnelData);
          this.funnel.draw();
      }
    }
    

    很多工作,但现在很酷的是,您可以在 TestBed 中注入 FunnelProvider 的 Mock 实现:

    TestBed
        .configureTestingModule({
          declarations: [
            FunnelComponent,
            FunnelStub
          ],
          providers: [
            { provide: FunnelProvider, useValue: MockFunnelProvider }
          ]
        })
    

    现在调用 funnelProvider.createFunnel 时,它不会返回真正的漏斗,而是您的漏斗存根。

    如果你必须在测试期间,你可以将组件漏斗字段转换为 FunnelStub(例如,如果它有一些特殊的方法):

    const funnelStub = component.funnel as FunnelStub;
    

    【讨论】:

    • 您建议的答案可能是我的问题的解决方案。但是,我宁愿不编辑我的FunnelComponent 只是为了使单元测试工作。我希望有一些 TestBed Karma/Jasmine-ish 方式告诉 TestBed 我想对使用 TestBed.createComponent 创建的组件使用另一个导入。
    • @Bishares :建议的答案不是只让单元测试工作。它关于使用依赖注入并将任务委托给服务,以便它们(组件和服务)都可以单独测试。这样你就可以隔离组件的依赖并用 Mock 替换。 medium.com/@shashankvivek.7/…
    • @ShashankVivek 说服了我。我的组件应该只包含模板/gui 逻辑,其他一切都可以委托给服务。因此,我的内部漏斗创建可以封装到服务中。
    • @Bishares :很高兴为您提供帮助。干杯!
    猜你喜欢
    • 2020-06-19
    • 2016-11-23
    • 2018-11-24
    • 1970-01-01
    • 1970-01-01
    • 2020-04-19
    • 1970-01-01
    • 1970-01-01
    • 2020-04-05
    相关资源
    最近更新 更多