Angular 2:如何在单元测试时模拟 ChangeDetectorRef [英] Angular 2: How to mock ChangeDetectorRef while unit testing

查看:12
本文介绍了Angular 2:如何在单元测试时模拟 ChangeDetectorRef的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我刚刚开始进行单元测试,我已经能够模拟我自己的服务以及一些 Angular 和 Ionic,但无论我做什么 ChangeDetectorRef 都保持不变.

我的意思是这是哪种魔法?

beforeEach(async(() =>TestBed.configureTestingModule({声明:[MyComponent],提供者:[表单、DomController、ToastController、AlertController、弹出控制器,{提供:平台,使用类:PlatformMock},{提供:导航参数,useValue: new NavParams({data: new PageData().Data})},{提供:ChangeDetectorRef,useClass:ChangeDetectorRefMock}],进口:[表单模块,反应形式模块,离子模块],}).overrideComponent(我的组件,{放: {提供者:[{提供:ChangeDetectorRef,useClass:ChangeDetectorRefMock},],视图提供者:[{提供:ChangeDetectorRef,useClass:ChangeDetectorRefMock},]}}).compileComponents().then(() => {让夹具 = TestBed.createComponent(MyComponent);让 cmp = fixture.debugElement.componentInstance;让 cdRef = fixture.debugElement.injector.get(ChangeDetectorRef);控制台.log(cdRef);//记录 ChangeDetectorRefMock控制台.log(cmp.cdRef);//记录 ChangeDetectorRef ,为什么?})));

<小时>

 it('无论如何都会失败', async(() => {spyOn(cdRef, 'markForCheck');spyOn(cmp.cdRef, 'markForCheck');cmp.ngOnInit();期望(cdRef.markForCheck).toHaveBeenCalled();//失败,为什么?期望(cmp.cdRef.markForCheck).toHaveBeenCalled();//成功控制台.log(cdRef);//记录 ChangeDetectorRefMock控制台.log(cmp.cdRef);//记录 ChangeDetectorRef ,为什么?}));

<小时>

@Component({...})导出类 MyComponent {构造函数(私有 cdRef:ChangeDetectorRef){}ngOnInit() {//做一点事this.cdRef.markForCheck();}}

我什么都试过了,async, fakeAsync, injector([ChangeDetectorRef], () => {}).p>

没有任何作用.

解决方案

2020年更新:

我最初是在 2017 年 5 月写的,这是一个在当时效果很好并且仍然有效的解决方案.

我们无法通过测试台配置 changeDetectorRef 模拟的注入,所以这就是我这些天在做的事情:

 it('检测变化', () => {//这是这里唯一的实例,全新的常量 changeDetectorRef = fixture.debugElement.injector.get(ChangeDetectorRef);//所以,我直接在原型上进行监视.常量 detectChangesSpy = spyOn(changeDetectorRef.constructor.prototype, 'detectChanges');组件.someMethod();//内部调用detectChanges.期望(detectChangesSpy).toHaveBeenCalled();});

那你就不用关心私有属性什么的了.


如果有人遇到这种情况,这是一种对我来说效果很好的方法:

当您在构造函数中注入 ChangeDetectorRef 实例时:

 构造函数(private cdRef: ChangeDetectorRef) { }

您将 cdRef 作为组件的私有属性之一,这意味着您可以监视组件,存根该属性并让它返回您想要的任何内容.此外,您可以根据需要断言其调用和参数.

在您的规范文件中,调用您的 TestBed 而不提供 ChangeDetectorRef,因为它不会提供您提供的内容.在每个块之前设置相同的组件,因此它在规范之间重置,因为它在文档中完成 这里:

component = fixture.componentInstance;

然后在测试中,直接窥探属性

describe('someMethod()', () => {it('调用检测变化', () => {const spy = spyOn((component as any).cdRef, 'detectChanges');组件.someMethod();期望(间谍).toHaveBeenCalled();});});

有了间谍,你可以使用 .and.returnValue() 并让它返回你需要的任何东西.

注意 (component as any) 被用作 cdRef 是一个私有属性.但是私有在实际编译的javascript中不存在,所以可以访问.

如果您想在运行时以这种方式访问​​私有属性以进行测试,这取决于您.

I have just started with Unit-Testing, and I have been able to mock my own services and some of Angular and Ionic as well, but no matter what I do ChangeDetectorRef stays the same.

I mean which kind of sorcery is this?

beforeEach(async(() => 
    TestBed.configureTestingModule({
      declarations: [MyComponent],
      providers: [
        Form, DomController, ToastController, AlertController,
        PopoverController,

        {provide: Platform, useClass: PlatformMock},
        {
          provide: NavParams,
          useValue: new NavParams({data: new PageData().Data})
        },
        {provide: ChangeDetectorRef, useClass: ChangeDetectorRefMock}

      ],
      imports: [
        FormsModule,
        ReactiveFormsModule,
        IonicModule
      ],
    })
    .overrideComponent(MyComponent, {
      set: {
        providers: [
          {provide: ChangeDetectorRef, useClass: ChangeDetectorRefMock},
        ],
        viewProviders: [
          {provide: ChangeDetectorRef, useClass: ChangeDetectorRefMock},
        ]
      }
    })
    .compileComponents()
    .then(() => {
      let fixture = TestBed.createComponent(MyComponent);
      let cmp = fixture.debugElement.componentInstance;

      let cdRef = fixture.debugElement.injector.get(ChangeDetectorRef);

      console.log(cdRef); // logs ChangeDetectorRefMock
      console.log(cmp.cdRef); // logs ChangeDetectorRef , why ??
    })
  ));


 it('fails no matter what', async(() => {
    spyOn(cdRef, 'markForCheck');
    spyOn(cmp.cdRef, 'markForCheck');

    cmp.ngOnInit();

    expect(cdRef.markForCheck).toHaveBeenCalled();  // fail, why ??
    expect(cmp.cdRef.markForCheck).toHaveBeenCalled(); // success

    console.log(cdRef); // logs ChangeDetectorRefMock
    console.log(cmp.cdRef); // logs ChangeDetectorRef , why ??
  }));


@Component({
  ...
})
export class MyComponent {
 constructor(private cdRef: ChangeDetectorRef){}

 ngOnInit() {
   // do something
   this.cdRef.markForCheck();
 }
}

I have tried everything , async, fakeAsync, injector([ChangeDetectorRef], () => {}).

Nothing works.

解决方案

Update 2020:

I wrote this originally in May 2017, it's a solution that worked great at the time and still works.

We can't configure the injection of a changeDetectorRef mock through the test bed, so this is what I am doing these days:

 it('detects changes', () => {
      // This is a unique instance here, brand new
      const changeDetectorRef = fixture.debugElement.injector.get(ChangeDetectorRef); 
     
      // So, I am spying directly on the prototype.
      const detectChangesSpy = spyOn(changeDetectorRef.constructor.prototype, 'detectChanges');

      component.someMethod(); // Which internally calls the detectChanges.

      expect(detectChangesSpy).toHaveBeenCalled();
    });

Then you don't care about private attributes or any.


In case anyone runs into this, this is one way that has worked well for me:

As you are injecting the ChangeDetectorRef instance in your constructor:

 constructor(private cdRef: ChangeDetectorRef) { }

You have that cdRef as one of the private attributes on the component, which means you can spy on the component, stub that attribute and have it return whatever you want. Also, you can assert its calls and parameters, as needed.

In your spec file, call your TestBed without providing the ChangeDetectorRef as it won't provide what you give it. Set the component that same beforeEach block, so it is reset between specs as it is done in the docs here:

component = fixture.componentInstance;

Then in the tests, spy directly on the attribute

describe('someMethod()', () => {
  it('calls detect changes', () => {
    const spy = spyOn((component as any).cdRef, 'detectChanges');
    component.someMethod();

    expect(spy).toHaveBeenCalled();
  });
});

With the spy you can use .and.returnValue() and have it return whatever you need.

Notice that (component as any) is used as cdRef is a private attribute. But private doesn't exist in the actual compiled javascript so it is accessible.

It is up to you if you want to access private attributes at runtime that way for your tests.

这篇关于Angular 2:如何在单元测试时模拟 ChangeDetectorRef的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆