【问题标题】:Angular2 unit test with @Input()Angular2 单元测试与@Input()
【发布时间】:2016-08-07 21:07:41
【问题描述】:

我有一个在实例变量上使用 @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}]);
    }
}

【问题讨论】:

  • 我写了一篇关于使用 @Input() 测试组件的简短博客,其中解释了测试所需输入的几种方法:medium.com/@AikoPath/…

标签: angular typescript unit-testing jasmine angular-test


【解决方案1】:

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

【讨论】:

  • 英雄元素在哪里使用
  • Aniruddha Das - 如果您绑定到 html 中英雄的任何属性,它将被使用。我有同样的问题,这个解决方案很容易实现,你可以在测试中创建一个模拟对象。这应该是公认的答案。
  • 使用 before each 来设置每个测试需要动态的数据对于编写需要测试多个特定案例的测试来说似乎是一种非常糟糕的模式
【解决方案2】:

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

【讨论】:

  • 我正在尝试您上面的建议。但是当我这样做时,我得到一个 "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';
  • 这看起来像是 Zone.js 错误,所以很难说。你在使用 Angular CLI 吗?或许提供一个链接,指向在您的控制台中记录的完整错误。
  • 我按照您的方法进行了测试,但是我的组件正在使用模板“

    ”进行测试,但它从未通过测试。一切正常,组件正确呈现,但未添加 html。如果我更改为

    {{ customFieldFormatted }}

    一切正常。不知道为什么 [outerHTML] 不起作用。你有什么主意吗?谢谢
  • @KimGentes,我相信,缺少某些提供程序配置,导致“未捕获的 ReferenceError:未定义区域”问题。在这种情况下,我所做的是在 TestBed.configureTestingModule() 周围添加 try-catch 块并将错误写入控制台。这表明缺少哪个提供者。只需添加此评论,以便将来对某人有所帮助。
  • 我认为这个答案需要改进,它并没有完全说明如何在包装组件上不使用静态产品,从而导致一个天真的人编写组件不同产品的每个测试用例的包装器作为输入。
【解决方案3】:

我通常会这样做:

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 类上的任何其他字段可以使用这种方法私有(这是我喜欢它而不是 Thierry 方法的主要原因,尽管事实上它是一个稍微详细一点)。

【讨论】:

【解决方案4】:

在您的测试中加载组件实例后,您需要在组件实例上设置 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。

【讨论】:

  • 在 OP 的示例中,设置的 @Input 属性是私有的。除非我弄错了,否则这种方法在这种情况下是行不通的,因为 tsc 会在对私有字段的引用上产生错误。
  • 感谢您指出这一点!我错过了该领域是私人的。我再次考虑了您的评论和“私人”方面。我想知道在这个字段上使用private 关键字是否是一件好事,因为它实际上不是“私有的”......我的意思是它将由 Angular2 从类外部更新。有兴趣听听你的意见;-)
  • 你问了一个有趣的问题,但我认为你必须问的真正问题是在打字稿中有private 是否是一件好事,因为它不是“实际上是私人的” - 即,因为它不能在运行时强制执行,只能在编译时强制执行。我个人喜欢它,但也理解反对它的说法。不过归根结底,微软选择在 TS 中使用它,而 Angular 选择 TS 作为主要语言,我认为我们不能直截了当地说使用主要语言的主要特性是一个坏主意。
  • 非常感谢您的回答!我个人坚信使用 TypeScript 是一件好事。它实际上有助于提高应用程序质量!我不认为使用private 是一件坏事,即使它在运行时不是真正私有的:-) 对于这种特殊情况,我不确定使用private 是一件好事,因为该领域由 Angular2 在课堂外管理...
  • 我正在尝试将它与新的 TestBed.createComponent 一起使用,但是当我调用 fixture.detectChanges() 时,它不会触发 ngOnChanges 调用。你知道如何用“新系统”测试它吗?
猜你喜欢
  • 1970-01-01
  • 2018-01-16
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-10-19
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多