使用@Input()进行Angular2单元测试


68

我有一个@Input()在实例变量上使用注解的组件,我正在尝试为该openProductPage()方法编写单元测试,但是我对设置单元测试的方式有些迷惑。我可以将该实例变量设置为公共变量,但是我认为我不必诉诸于此。

如何设置茉莉花测试,以便注射(提供?)模拟产品,然后可以测试该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}]);
    }
}

2
我写了一个简短的博客,介绍了使用@Input()测试组件的过程,该博客解释了测试所需输入的几种方法:medium.com/@AikoPath/…–
BraveHeart

Answers:


16

我通常会做类似的事情:

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 
}

请注意,使用这种方法productProductThumbnail班级中的任何其他字段都可以是私有的(这是我比Thierry的方法更喜欢它的主要原因,尽管它稍微冗长一些)。


您还需要注入TestComponentBuilder吗?参见:medium.com/@AikoPath/…–
BraveHeart

对于寻求“纯测试台”方法的开发人员,本文中有一些答案: stackoverflow.com/a/36655501/301603stackoverflow.com/a/43755910/301603 这个特定的答案没有错,但是更多。比真正的单元测试方法更“ hack”
EdgarZagórski17年

49

这是来自官方文档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
});

6
英雄元素在哪里使用
Aniruddha Das'Aug

Aniruddha Das-如果您绑定到html中英雄的任何属性,将使用它。我完全有同样的问题,该解决方案易于实现,您可以在测试中在此处创建一个模拟对象。这应该是公认的答案。
院长

在编写每个需要测试多个特定情况之外的测试的测试之前,在每次设置之前使用需要设置动态数据似乎是一种非常糟糕的模式
船长Prinny

48

如果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()
}

我正在尝试上面您的建议。.但是,当我这样做时,我收到“未捕获的ReferenceError:未定义区域”。我正在使用上面显示的代码的虚拟克隆。(加上以下内容,包括我自己的内容): 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';
Kim Gentes

看起来好像是Zone.js错误,所以很难说。您正在使用Angular CLI吗?也许提供指向登录到控制台的完整错误的链接。
丹尼·布利斯

我遵循了您的方法,但是正在测试我的组件具有模板'<p [outerHTML] =“ customFieldFormatted”> </ p>',并且它从未通过测试。一切正常,组件正确呈现,但未添加html。如果我更改为<p> {{customFieldFormatted}} </ p>,则一切正常。不知道为什么[outerHTML]不起作用。你有什么主意吗?谢谢
亚历克斯·里尔佐索夫'17

@KimGentes,我相信缺少某些提供程序配置,导致出现“ Uncaught ReferenceError:未定义区域”问题。在这种情况下,我要做的是在周围添加try-catch块TestBed.configureTestingModule()并将错误写入控制台。这表明缺少哪个提供程序。只需添加此评论,以便将来对他人有所帮助。
ramtech '18

我认为这个答案有待改进,它并不能一路说明如何在包装器组件上不使用静态产品,从而导致幼稚的人为每个不同产品的测试用例编写一个组件包装器作为输入。
普林尼船长

23

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

将此示例视为示例:https ://plnkr.co/edit/YAVD4s?p = preview 。


3
在OP的示例中,@Input要设置的属性是私有的。除非我没有记错,否则这种方法在那种情况下将行不通,因为tsc将禁止引用私有字段。
在2016年

1
感谢您指出了这一点!我想念这个领域是私人的。我再次考虑了您的评论和“私人”方面。我不知道private在此字段上添加关键字是否是一件好事,因为它实际上不是“私有”的……我的意思是它将由Angular2从类外部进行更新。会对您的意见感兴趣;-)
Thierry Templier 2016年

2
您问了一个有趣的问题,但是我认为您接下来要问的真正问题是private,因为它不是“实际上是私有的”,所以根本不用打字稿是一件好事-即,因为它不能在运行时强制执行,在编译时。我个人喜欢它,但也理解反对它的观点。归根结底,微软选择将其包含在TS中,而Angular选择了TS作为主要语言,我认为我们不能断然地说使用主要语言的主要功能是一个坏主意。
在2016年

2
非常感谢您的回答!我个人坚信使用TypeScript是一件好事。它实际上有助于提高应用程序质量!我认为private即使在运行时不是真正私有的,使用它也不是一件坏事:-)对于这种特殊情况,我不确定该使用什么好东西,private因为该字段是由类外部管理的Angular2 ...
Thierry Templier

2
我正在尝试将其与新的TestBed.createComponent一起使用,但是当我调用Fixture.detectChanges()时,它不会触发ngOnChanges调用。您知道如何使用“新系统”对其进行测试吗?
bucicimaci '16
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.