Angular 角度4/5渲染器2茉莉花测试
我有一个接口,它打算基于输入参数对象操作DOM元素。我试图先编写单元测试。想法是使用Render2在Angular中可用Angular 角度4/5渲染器2茉莉花测试,angular,typescript,angular5,Angular,Typescript,Angular5,我有一个接口,它打算基于输入参数对象操作DOM元素。我试图先编写单元测试。想法是使用Render2在Angular中可用 export interface ModifyDomTree { modify(data: SomeData) : ElementRef; } 这是一个实现(不知道如何实现),但编写测试是为了它 export class ModifyDomTreeImpl implements ModifyDomTree { constructor(private rende
export interface ModifyDomTree {
modify(data: SomeData) : ElementRef;
}
这是一个实现(不知道如何实现),但编写测试是为了它
export class ModifyDomTreeImpl implements ModifyDomTree {
constructor(private render: Renderer2) {
}
modify(data: SomeData) : ElementRef{
return null;
}
}
在测试中,我不想使用渲染器2的模拟,我想使用Angular将使用的实际渲染器2。如何在测试中注入或实例化实际角度渲染2
规格是
describe('ModifyDomTreeImpl', () => {
let data: SomeData;
let modifyDomTree: ModifyDomTree;
beforeEach(() => {
//let render: Renderer2 = mock(Renderer2); ?? How Do I inject the real Angular thing here
modifyDomTree = new ModifyDomTreeImpl(render);
});
it('should convert a data into a text node', () => {
data = mock(SomeData);
when(data.value).thenReturn('Groot!!');
const result: ElementRef = modifyDomTree.convert(data);
expect(result).toBeDefined();
expect(result).not.toBeNull();
expect(result.nativeElement).toBeDefined();
expect(result.nativeElement).toBeDefined();
expect(result.nativeElement.childNodes).toBeDefined();
expect(result.nativeElement.childNodes.length).toEqual(1);
expect(result.nativeElement.childNodes.length[0]).toEqual('text');
expect(result.nativeElement.childNodes.length[0].data).toEqual('Groot!!');
});
});
直接使用角度声音操纵DOM元素是一个非常糟糕的主意。 我会首先尝试理解我真正想要实现什么,以及是否有比动态修改dom更好的方法来实现它。angular的基本原理是避免直接进行DOM操作` 我明白这可能不容易理解。。。因此,您必须考虑实现中的组件。在本例中,您有一些机箱和一些组件适合这些机箱(或带有一些选项的机箱)。所有选项都以div/css格式提供。但仅在需要时启用/可见(模板中的ngIf条件)。想象一下,您已经实现并提供了所有选项,只是进行了定制。在这种情况下,这并不禁止仅为在页面创建/或服务更新时创建不同类型的组件而实现DOM操纵(通过角度): 例如:
import { Component, OnInit , ComponentFactoryResolver, ViewContainerRef, ViewChild} from '@angular/core';
import { NicolabelComponent } from './nicolabel/nicolabel.component';
@Component({
selector: 'app-nicoview',
templateUrl: '
<button (click)="addNicoLabel()">Add </button>
<div #mydiv style="width:100px;height:200px">;
</div>
',
styles: []
})
export class NicoviewComponent implements OnInit {
@ViewChild('mydiv', {read: ViewContainerRef}) mydiv;
labels: NicolabelComponent[] = [];
ngOnInit() {
}
// `ViewContainerRef` from the component itself
constructor(private viewContainerRef:ViewContainerRef, private componentFactoryResolver: ComponentFactoryResolver) {}
labelClicked(label)
{
console.log("click : ");
console.log(label.text);
}
addNicoLabel()
{
console.log("clicked");
// to be created dynamicalaly this component must be declared in app.module.ts as an entryComponents
let factory = this.componentFactoryResolver.resolveComponentFactory(NicolabelComponent);
let created_component = this.mydiv.createComponent(factory);
// this.labels.push(created_component );
}
}
从'@angular/core'导入{Component,OnInit,ComponentFactoryResolver,ViewContainerRef,ViewChild};
从“./nicolabel/nicolabel.component”导入{NicolabelComponent};
@组成部分({
选择器:“应用程序视图”,
templateUrl:'
添加
;
',
样式:[]
})
导出类NicoviewComponent实现OnInit{
@ViewChild('mydiv',{read:ViewContainerRef})mydiv;
标签:NicolableComponent[]=[];
恩戈尼尼特(){
}
//来自组件本身的“ViewContainerRef”
构造函数(私有viewContainerRef:viewContainerRef,私有componentFactoryResolver:componentFactoryResolver){}
标签点击(标签)
{
console.log(“单击:”);
console.log(label.text);
}
addNicoLabel()
{
控制台日志(“单击”);
//要动态创建此组件,必须在app.module.ts中将其声明为entryComponents
让工厂=this.componentFactoryResolver.resolveComponentFactory(NicolableComponent);
让created_component=this.mydiv.createComponent(工厂);
//this.labels.push(创建的组件);
}
}
这应该适用于角度6+
在component.spec.ts中,使用提供程序注入渲染器。
然后,您可以检索并监视它以确认测试
let renderer2: Renderer2;
...
beforeEach(async( () => {
TestBed.configureTestingModule({
...
providers: [Renderer2] // This is the angular renderer
}).compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(YourComponent);
// grab the renderer
renderer2 = fixture.componentRef.injector.get<Renderer2>(Renderer2 as Type<Renderer2>);
// and spy on it
spyOn(renderer2, 'addClass').and.callThrough();
// or replace
spyOn(renderer2, 'addClass').and.callFake(..);
// etc
});
it('should call renderer', () => {
expect(renderer2.addClass).toHaveBeenCalledWith(jasmine.any(Object), 'css-class');
});
let render2:render2;
...
beforeach(异步(()=>{
TestBed.configureTestingModule({
...
提供者:[Renderer2]//这是角度渲染器
}).compileComponents();
}));
在每个之前(()=>{
fixture=TestBed.createComponent(您的组件);
//抓取渲染器
Renderr2=fixture.componentRef.injector.get(Renderr2作为类型);
//监视它
spyOn(renderer2,'addClass')。和.callThrough();
//或替换
spyOn(renderer2,'addClass')。和.callFake(…);
//等
});
它('应该调用渲染器',()=>{
expect(renderr2.addClass).tohavencalledwith(jasmine.any(Object),'css class');
});
最直接的方法是使其成为测试台测试。但为什么你需要“真正有棱角的东西”呢expect(result.nativeElement)
,等等——框架本身已经对其进行了测试。这就是我们使用框架的原因。因为我们可以依靠他们,节省时间。您只需要检查renderr2是否已实例化,其方法是否使用正确的参数调用。我有一个指令,使contenteditable div.成为从后端服务获取数据的组件。这个特定的服务修改来自后端的数据并将其放回div中。该场景对于单元测试来说太复杂了。在集成测试中,您肯定需要使用TestBed,而不是手动实例化类。但是如何在TestBed中注入真正的Render2呢?configureTestingModule({providers:[ModifyDomainTreeImpl,Render2]});这没有帮助。render只是一个空对象{}。哪个模块将具有此Render2?因为它是编译器依赖项,并且仅在组件注入器中可用。您需要为此创建虚拟组件,并在其中注入ModifyDomtreImpl。这就是为什么使用存根进行测试要干净得多的原因。我正在尝试从框架中分离出来。我有一个需要处理contenteditable div的用例。想想自定义编辑器吧。dom操作服务需要根据特定的业务逻辑创建不同的元素或添加css等。因此,这个特定的服务将数据输出为dom树。为了确保一切正常工作,特别是在Rendere2仍处于实验状态的情况下,我想确保测试覆盖了圆顶的创建/修改,并输出正确的dom树。