Angular 角度4/5渲染器2茉莉花测试

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

我有一个接口,它打算基于输入参数对象操作DOM元素。我试图先编写单元测试。想法是使用Render2在Angular中可用

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树。