angular2 test,我如何模拟子组件

时间:2016-03-13 21:06:23

标签: unit-testing testing jasmine angular components

如何在茉莉花测试中模拟子组件?

我有MyComponent,其使用MyNavbarComponentMyToolbarComponent

import {Component} from 'angular2/core';
import {MyNavbarComponent} from './my-navbar.component';
import {MyToolbarComponent} from './my-toolbar.component';

@Component({
  selector: 'my-app',
  template: `
    <my-toolbar></my-toolbar>
    {{foo}}
    <my-navbar></my-navbar>
  `,
  directives: [MyNavbarComponent, MyToolbarComponent]
})
export class MyComponent {}

当我测试这个组件时,我不想加载和测试这两个子组件; MyNavbarComponent,MyToolbarComponent,所以我想模仿它。

我知道如何使用provide(MyService, useClass(...))模拟服务,但我不知道如何模拟指令;部件;

  beforeEach(() => {
    setBaseTestProviders(
      TEST_BROWSER_PLATFORM_PROVIDERS,
      TEST_BROWSER_APPLICATION_PROVIDERS
    );

    //TODO: want to mock unnecessary directives for this component test
    // which are MyNavbarComponent and MyToolbarComponent
  })

  it('should bind to {{foo}}', injectAsync([TestComponentBuilder], (tcb) => {
    return tcb.createAsync(MyComponent).then((fixture) => {
      let DOM = fixture.nativeElement;
      let myComponent = fixture.componentInstance;
      myComponent.foo = 'FOO';
      fixture.detectChanges();
      expect(DOM.innerHTML).toMatch('FOO');
    });
  });

这是我的plunker示例;

http://plnkr.co/edit/q1l1y8?p=preview

4 个答案:

答案 0 :(得分:60)

根据要求,我发布了另一个关于如何使用input / output模拟子组件的答案:

所以让我们首先说我们有TaskListComponent显示任务,并在点击其中一个时刷新:

<div id="task-list">
  <div *ngFor="let task of (tasks$ | async)">
    <app-task [task]="task" (click)="refresh()"></app-task>
  </div>
</div>

app-task是一个带有[task]输入和(click)输出的子组件。

好的,现在我们想为我的TaskListComponent编写测试,当然我们不想测试真正的app-task组件。

因为@Klas建议我们可以使用以下内容配置TestModule

schemas: [CUSTOM_ELEMENTS_SCHEMA]

我们可能在构建或运行时都没有出现任何错误,但除了子组件的存在外,我们还无法测试其他错误。

那么我们如何模拟子组件?

首先,我们将为我们的子组件(相同的选择器)定义一个模拟指令:

@Directive({
  selector: 'app-task'
})
class MockTaskDirective {
  @Input('task')
  public task: ITask;
  @Output('click')
  public clickEmitter = new EventEmitter<void>();
}

现在我们将在测试模块中声明它:

let fixture : ComponentFixture<TaskListComponent>;
let cmp : TaskListComponent;

beforeEach(() => {
  TestBed.configureTestingModule({
    declarations: [TaskListComponent, **MockTaskDirective**],
    // schemas: [CUSTOM_ELEMENTS_SCHEMA],
    providers: [
      {
        provide: TasksService,
        useClass: MockService
      }
    ]
  });

  fixture = TestBed.createComponent(TaskListComponent);
  **fixture.autoDetectChanges();**
  cmp = fixture.componentInstance;
});
  • 请注意,由于夹具的子组件的生成在创建后异步发生,因此我们激活其autoDetectChanges功能。

在我们的测试中,我们现在可以查询该指令,访问其DebugElement的注入器,并通过它获取我们的模拟指令实例:

import { By } from '@angular/platform-browser';    
const mockTaskEl = fixture.debugElement.query(By.directive(MockTaskDirective));
const mockTaskCmp = mockTaskEl.injector.get(MockTaskDirective) as MockTaskDirective;

[此部分通常应位于beforeEach部分,以获得更清晰的代码。]

从这里开始,测试是小菜一碟:)

it('should contain task component', ()=> {
  // Arrange.
  const mockTaskEl = fixture.debugElement.query(By.directive(MockTaskDirective));

  // Assert.
  expect(mockTaskEl).toBeTruthy();
});

it('should pass down task object', ()=>{
  // Arrange.
  const mockTaskEl = fixture.debugElement.query(By.directive(MockTaskDirective));
  const mockTaskCmp = mockTaskEl.injector.get(MockTaskDirective) as MockTaskDirective;

  // Assert.
  expect(mockTaskCmp.task).toBeTruthy();
  expect(mockTaskCmp.task.name).toBe('1');
});

it('should refresh when task is clicked', ()=> {
  // Arrange
  spyOn(cmp, 'refresh');
  const mockTaskEl = fixture.debugElement.query(By.directive(MockTaskDirective));
  const mockTaskCmp = mockTaskEl.injector.get(MockTaskDirective) as MockTaskDirective;

  // Act.
  mockTaskCmp.clickEmitter.emit();

  // Assert.
  expect(cmp.refresh).toHaveBeenCalled();
});

答案 1 :(得分:23)

如果您在TestBed中使用import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core'; import { TestBed, async } from '@angular/core/testing'; import { MyComponent } from './my.component'; describe('App', () => { beforeEach(() => { TestBed .configureTestingModule({ declarations: [ MyComponent ], schemas: [CUSTOM_ELEMENTS_SCHEMA] }); }); it(`should have as title 'app works!'`, async(() => { let fixture = TestBed.createComponent(MyComponent); let app = fixture.debugElement.componentInstance; expect(app.title).toEqual('Todo List'); })); }); ,则测试中的组件将不会加载子组件。

CUSTOM_ELEMENTS_SCHEMA

这适用于Angular 2.0的发布版本。 Full code sample here

NO_ERRORS_SCHEMA的替代方法是Flow<Query, Reply> flow = new Flow<Query, Reply>(query, reply);

答案 2 :(得分:7)

感谢Eric Martinez,我找到了这个解决方案。

我们可以使用此处记录的overrideDirective函数, https://angular.io/docs/ts/latest/api/testing/TestComponentBuilder-class.html

需要三个prarmeters; 1.要实施的组件 2.要覆盖的子组件 3.模拟组件

已解决的解决方案位于http://plnkr.co/edit/a71wxC?p=preview

这是来自plunker的代码示例

import {MyNavbarComponent} from '../src/my-navbar.component';
import {MyToolbarComponent} from '../src/my-toolbar.component';

@Component({template:''})
class EmptyComponent{}

describe('MyComponent', () => {

  beforeEach(injectAsync([TestComponentBuilder], (tcb) => {
    return tcb
      .overrideDirective(MyComponent, MyNavbarComponent, EmptyComponent)
      .overrideDirective(MyComponent, MyToolbarComponent, EmptyComponent)
      .createAsync(MyComponent)
      .then((componentFixture: ComponentFixture) => {
        this.fixture = componentFixture;
      });
  ));

  it('should bind to {{foo}}', () => {
    let el = this.fixture.nativeElement;
    let myComponent = this.fixture.componentInstance;
    myComponent.foo = 'FOO';
    fixture.detectChanges();
    expect(el.innerHTML).toMatch('FOO');    
  });
});

答案 3 :(得分:5)

我整理了一个简单的MockComponent模块,以帮助简化这一过程:

import { TestBed } from '@angular/core/testing';
import { MyComponent } from './src/my.component';
import { MockComponent } from 'ng2-mock-component';

describe('MyComponent', () => {

  beforeEach(() => {

    TestBed.configureTestingModule({
      declarations: [
        MyComponent,
        MockComponent({ 
          selector: 'my-subcomponent', 
          inputs: ['someInput'], 
          outputs: [ 'someOutput' ]
        })
      ]
    });

    let fixture = TestBed.createComponent(MyComponent);
    ...
  });

  ...
});

它可以在 https://www.npmjs.com/package/ng2-mock-component