angular2 测试,如何模拟子组件
angular2 test, how do I mock sub component
如何在 jasmine 测试中模拟子组件?
我有 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 示例;
感谢 Eric Martinez,我找到了这个解决方案。
我们可以使用此处记录的 overrideDirective
函数,
https://angular.io/docs/ts/latest/api/testing/TestComponentBuilder-class.html
需要三个参数;
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');
});
});
如果您在 TestBed
中使用 schemas: [CUSTOM_ELEMENTS_SCHEMA]
,被测组件将不会加载子组件。
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 的发布版本。
Full code sample here.
CUSTOM_ELEMENTS_SCHEMA
的替代方法是 NO_ERRORS_SCHEMA
根据要求,我发布了另一个关于如何使用 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;
});
- 请注意,由于 fixture 的子组件的生成是在其创建后异步发生的,因此我们激活了它的 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();
});
我整理了一个简单的 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);
...
});
...
});
如何在 jasmine 测试中模拟子组件?
我有 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 示例;
感谢 Eric Martinez,我找到了这个解决方案。
我们可以使用此处记录的 overrideDirective
函数,
https://angular.io/docs/ts/latest/api/testing/TestComponentBuilder-class.html
需要三个参数; 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');
});
});
如果您在 TestBed
中使用 schemas: [CUSTOM_ELEMENTS_SCHEMA]
,被测组件将不会加载子组件。
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 的发布版本。 Full code sample here.
CUSTOM_ELEMENTS_SCHEMA
的替代方法是 NO_ERRORS_SCHEMA
根据要求,我发布了另一个关于如何使用 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;
});
- 请注意,由于 fixture 的子组件的生成是在其创建后异步发生的,因此我们激活了它的 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();
});
我整理了一个简单的 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);
...
});
...
});