Подтвердить что ты не робот

Angular2 тест, как я подделываю подкомпонент

Как я подделываю подкомпонент в тестах жасмина?

У меня есть MyComponent, который использует MyNavbarComponent и MyToolbarComponent

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

4b9b3361

Ответ 1

В соответствии с запросом, я отправляю еще один ответ о том, как издеваться над субкомпонентами с помощью 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();
});

Ответ 2

Если вы используете schemas: [CUSTOM_ELEMENTS_SCHEMA] в 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');
  }));

});

Это работает в выпущенной версии Angular 2.0. Полный пример кода здесь.

Альтернативой CUSTOM_ELEMENTS_SCHEMA является NO_ERRORS_SCHEMA

Ответ 3

Благодаря Эрику Мартинесу я нашел это решение.

Мы можем использовать функцию 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');    
  });
});

Ответ 4

Я собрал простой модуль 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.