web-dev-qa-db-fra.com

test angular2, comment puis-je me moquer d'un sous-composant

Comment puis-je simuler un composant secondaire dans des tests au jasmin?

J'ai MyComponent, qui utilise MyNavbarComponent et 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 {}

Lorsque je teste ce composant, je ne souhaite pas charger et tester ces deux sous-composants; MyNavbarComponent, MyToolbarComponent, je veux donc m'en moquer.

Je sais me moquer de services utilisant provide(MyService, useClass(...)), mais je ne sais pas comment me moquer de directives. Composants;

  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');
    });
  });

Voici mon exemple de plunker;

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

50
allenhwkim

Comme demandé, je poste une autre réponse sur la façon de simuler des sous-composants avec input/output:

Commençons donc par dire que nous avons TaskListComponent qui affiche les tâches et actualise chaque fois que l’on clique sur l’une d’elles:

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

app-task est un sous-composant avec le [task] _ entrée et le (click) sortie.

Ok super, maintenant nous voulons écrire des tests pour mon TaskListComponent et bien sûr nous ne voulons pas tester le vrai app-taskcomposant.

comme @Klas l’a suggéré, nous pouvons configurer notre TestModule avec:

schemas: [CUSTOM_ELEMENTS_SCHEMA]

Il est possible que nous n'obtenions aucune erreur lors de la compilation ou de l'exécution, mais nous ne pourrons pas en tester beaucoup plus que l'existence du sous-composant.

Alors, comment pouvons-nous nous moquer de sous-composants?

Commençons par définir une directive fictive pour notre sous-composant (même sélecteur):

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

Maintenant, nous allons le déclarer dans le module de test:

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;
});
  • Notez que comme la génération du sous-composant de la fixture a lieu de manière asynchrone après sa création, nous activons sa fonctionnalité autoDetectChanges.

Dans nos tests, nous pouvons maintenant interroger la directive, accéder à son injecteur DebugElement et obtenir notre instance de directive fictive:

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

[Cette partie devrait normalement figurer dans la section beforeEach, pour un code plus propre.]

À partir de là, les tests sont un jeu d'enfant :)

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();
});
60
baryo

Si tu utilises schemas: [CUSTOM_ELEMENTS_SCHEMA]in TestBed le composant à tester ne chargera pas de sous-composants.

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');
  }));

});

Cela fonctionne dans la version finale de Angular 2.0. exemple de code complet ici) .

Une alternative à CUSTOM_ELEMENTS_SCHEMA est NO_ERRORS_SCHEMA

23
Klas Mellbourn

Grâce à Eric Martinez, j'ai trouvé cette solution.

Nous pouvons utiliser la fonction overrideDirective qui est documentée ici, https://angular.io/docs/ts/latest/api/testing/TestComponentBuilder-class.html

Il faut trois prarmeters; 1. Composant à implémenter 2. Composant enfant à remplacer 3. Composant simulé

La solution résolue est ici à http://plnkr.co/edit/a71wxC?p=preview

Ceci est l'exemple de code du plongeur

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');    
  });
});
7
allenhwkim

J'ai mis en place un simple module MockComponent pour rendre cela un peu plus facile:

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);
    ...
  });

  ...
});

Il est disponible sur https://www.npmjs.com/package/ng2-mock-component .

5
Christian Nunciato