Модульный тест Angular2 с @Input ()

У меня есть компонент, который использует аннотацию @Input() для переменной экземпляра, и я пытаюсь написать свой модульный тест для метода openProductPage(), но я немного запутался в том, как настроить свой модульный тест. Я мог сделать эту переменную экземпляра общедоступной, но не думаю, что мне нужно прибегать к этому.

Как мне настроить мой тест Jasmine так, чтобы вводился имитационный продукт (предоставляется?), И я мог протестировать метод openProductPage()?

Мой компонент:

import {Component, Input} from "angular2/core";
import {Router} from "angular2/router";

import {Product} from "../models/Product";

@Component({
    selector: "product-thumbnail",
    templateUrl: "app/components/product-thumbnail/product-thumbnail.html"
})

export class ProductThumbnail {
    @Input() private product: Product;


    constructor(private router: Router) {
    }

    public openProductPage() {
        let id: string = this.product.id;
        this.router.navigate([“ProductPage”, {id: id}]);
    }
}

person hartpdx    schedule 15.04.2016    source источник
comment
Я написал небольшой блог о тестировании компонентов с помощью @Input (), в котором объясняется несколько способов тестирования ввода, который вы хотите: medium.com/@AikoPath/   -  person BraveHeart    schedule 22.06.2017


Ответы (4)


Обычно я делаю что-то вроде:

describe('ProductThumbnail', ()=> {
  it('should work',
    injectAsync([ TestComponentBuilder ], (tcb: TestComponentBuilder) => {
      return tcb.createAsync(TestCmpWrapper).then(rootCmp => {
        let cmpInstance: ProductThumbnail =  
               <ProductThumbnail>rootCmp.debugElement.children[ 0 ].componentInstance;

        expect(cmpInstance.openProductPage()).toBe(/* whatever */)
      });
  }));
}

@Component({
 selector  : 'test-cmp',
 template  : '<product-thumbnail [product]="mockProduct"></product-thumbnail>',
 directives: [ ProductThumbnail ]
})
class TestCmpWrapper { 
    mockProduct = new Product(); //mock your input 
}

Обратите внимание, что product и любые другие поля в классе ProductThumbnail могут быть частными при таком подходе (это основная причина, по которой я предпочитаю его подходу Тьерри, несмотря на то, что он немного более подробный).

person drew moore    schedule 15.04.2016
comment
Вам все еще нужно внедрить TestComponentBuilder? см. medium.com/@AikoPath/ - person BraveHeart; 22.06.2017
comment
Для разработчиков, которые ищут чистый тестовый подход, в этом посте есть несколько ответов: stackoverflow.com/a/36655501/301603 и stackoverflow.com/a/43755910/301603 Этот конкретный ответ не является неправильным, но это скорее «взлом» чем реальный подход к модульному тестированию - person Edgar Zagórski; 24.11.2017

это из официальной документации https://angular.io/docs/ts/latest/guide/testing.html#!#component-fixture. Таким образом, вы можете создать новый объект ввода expectedHero и передать его компоненту comp.hero = expectedHero.

Также не забудьте вызвать fixture.detectChanges(); последним, иначе свойство не будет привязано к компоненту.

Рабочий пример

// async beforeEach
beforeEach( async(() => {
    TestBed.configureTestingModule({
        declarations: [ DashboardHeroComponent ],
    })
    .compileComponents(); // compile template and css
}));

// synchronous beforeEach
beforeEach(() => {
    fixture = TestBed.createComponent(DashboardHeroComponent);
    comp    = fixture.componentInstance;
    heroEl  = fixture.debugElement.query(By.css('.hero')); // find hero element

    // pretend that it was wired to something that supplied a hero
    expectedHero = new Hero(42, 'Test Name');
    comp.hero = expectedHero;
    fixture.detectChanges(); // trigger initial data binding
});
person Vazgen Manukyan    schedule 03.05.2017
comment
где используется элемент героя - person Aniruddha Das; 24.08.2017
comment
Aniruddha Das - будет использоваться, если вы привяжете к каким-либо свойствам героя в html. У меня была точно такая же проблема, и это решение легко реализовать, и вы можете создать фиктивный объект прямо здесь, в тесте. Это должен быть принятый ответ. - person Dean; 07.02.2018
comment
Использование перед каждым для установки данных, которые должны быть динамическими для каждого теста, кажется действительно плохим шаблоном для написания тестов, которые должны проверять что-либо более чем в одном конкретном случае. - person Captain Prinny; 13.08.2020

Если вы используете TestBed.configureTestingModule для компиляции тестового компонента, вот другой подход. Это в основном то же самое, что и принятый ответ, но может быть больше похоже на то, как angular-cli генерирует спецификации. FWIW.

import { Component, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { DebugElement } from '@angular/core';

describe('ProductThumbnail', () => {
  let component: ProductThumbnail;
  let fixture: ComponentFixture<TestComponentWrapper>;

  beforeEach(async(() => {
    TestBed.configureTestingModule({
      declarations: [ 
        TestComponentWrapper,
        ProductThumbnail
      ],
      schemas: [CUSTOM_ELEMENTS_SCHEMA]
    })
    .compileComponents();

    fixture = TestBed.createComponent(TestComponentWrapper);
    component = fixture.debugElement.children[0].componentInstance;
    fixture.detectChanges();
  });

  it('should create', () => {
    expect(component).toBeTruthy();
  });
});

@Component({
  selector: 'test-component-wrapper',
  template: '<product-thumbnail [product]="product"></product-thumbnail>'
})
class TestComponentWrapper {
  product = new Product()
}
person Danny Bullis    schedule 13.12.2016
comment
Я пробую то, что вы предлагаете выше ... но когда я это делаю, я получаю Uncaught ReferenceError: Zone is not defined. Я использую виртуальный клон кода, который вы показали выше. (с добавлением моих собственных, как показано ниже): import { Component, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core'; import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { testContentNavData } from './mok-definitions'; import { ContentNavComponent } from '../app/content-nav/content-nav.component'; import {} from 'jasmine'; - person Kim Gentes; 12.01.2017
comment
Это похоже на ошибку Zone.js, поэтому трудно сказать. Вы используете Angular CLI? Возможно, предоставьте ссылку на полную версию ошибки при входе в консоль. - person Danny Bullis; 08.02.2017
comment
Я последовал вашему подходу, однако мой компонент тестируется с шаблоном '‹p [outerHTML] = customFieldFormatted› ‹/p›', и он никогда не проходит тесты. Все работает нормально, компонент отображается правильно, но html не добавляется. Если я перейду на ‹p› {{customFieldFormatted}} ‹/p›, все будет работать нормально. Не уверен, почему [externalHTML] не работает. Есть ли у вас какие-либо идеи? Спасибо - person Alex Ryltsov; 19.09.2017
comment
@KimGentes, я считаю, что некоторая конфигурация провайдера отсутствует, что привело к проблеме «Uncaught ReferenceError: Zone is not defined». Что я делаю в таком сценарии, так это добавляю блок try-catch вокруг TestBed.configureTestingModule() и записываю ошибку в консоль. Это показывает, какой провайдер отсутствует. Просто добавляю этот комментарий, чтобы в будущем он мог кому-то помочь. - person ramtech; 18.01.2018
comment
Я думаю, что этот ответ нужно улучшить, он не полностью демонстрирует, как не использовать статический продукт в компоненте-оболочке, что заставляет наивного человека писать оболочку компонента для каждого тестового примера отдельного продукта. как вход. - person Captain Prinny; 13.08.2020

Вам необходимо установить значение product в экземпляре компонента после того, как он был загружен в ваш тест.

В качестве образца здесь представлен простой компонент во входных данных, который вы можете использовать в качестве основы для своего варианта использования:

@Component({
  selector: 'dropdown',
  directives: [NgClass],
  template: `
    <div [ngClass]="{open: open}">
    </div>
  `,
})
export class DropdownComponent {
  @Input('open') open: boolean = false;

  ngOnChanges() {
    console.log(this.open);
  }
}

И соответствующий тест:

it('should open', injectAsync([TestComponentBuilder], (tcb: TestComponentBuilder) => {
  return tcb.createAsync(DropdownComponent)
  .then(fixture => {
    let el = fixture.nativeElement;
    let comp: DropdownComponent = fixture.componentInstance;

    expect(el.className).toEqual('');

    // Update the input
    comp.open = true; // <-----------

    // Apply
    fixture.detectChanges(); // <-----------

    var div = fixture.nativeElement.querySelector('div');
    // Test elements that depend on the input
    expect(div.className).toEqual('open');
  });
}));

См. Этот plunkr как образец: https://plnkr.co/edit/YAVD4s?p=preview < / а>.

person Thierry Templier    schedule 15.04.2016
comment
В примере OP устанавливаемое свойство @Input является частным. Если я не ошибаюсь, этот подход не сработает в этом случае, потому что tsc собирается заблокировать ссылку на частное поле. - person drew moore; 15.04.2016
comment
Спасибо за указание на это! Я скучал по тому, что поле было частным. Я снова подумал о вашем комментарии и о личном аспекте. Интересно, хорошо ли иметь ключевое слово private в этом поле, поскольку оно на самом деле не закрыто ... Я имею в виду, что оно будет обновляться вне класса с помощью Angular2. Было бы интересно узнать ваше мнение ;-) - person Thierry Templier; 17.04.2016
comment
вы задаете интересный вопрос, но я думаю, что реальный вопрос, который вы должны тогда задать, заключается в том, хорошо ли вообще иметь private в машинописном тексте, поскольку он на самом деле не является частным, т. е. поскольку он не может применяться во время выполнения, только в время компиляции. Мне лично это нравится, но я также понимаю аргументы против. В конце концов, Microsoft выбрала TS в качестве основного языка, а Angular выбрала TS в качестве основного языка, и я не думаю, что мы можем категорически сказать, что использование основных функций основного языка - плохая идея. - person drew moore; 18.04.2016
comment
Большое спасибо за ответ! Я лично убежден, что использование TypeScript - это хорошо. Это действительно способствует повышению качества приложений! Я не думаю, что использование private - это плохо, даже если оно не совсем приватное во время выполнения :-) В этом конкретном случае я не уверен, что это хорошо для использования private, поскольку поле управляется вне класс от Angular2 ... - person Thierry Templier; 20.04.2016
comment
Я пытаюсь использовать его с новым TestBed.createComponent, но когда я вызываю fixture.detectChanges (), он не запускает вызов ngOnChanges. Вы знаете, как я могу протестировать его с новой системой? - person bucicimaci; 07.10.2016