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

我是说这是什么魔法?

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();
 }
}

我什么都试过了,asyncfakeAsyncinjector([ChangeDetectorRef], () => {}).

什么都不管用.

推荐答案

Update 2020:

我最初是在2017年5月写这篇文章的,这是一个在当时非常有效并且仍然有效的解决方案.

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

 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();
    });

那你就不在乎私有属性或任何.


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

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

 constructor(private cdRef: ChangeDetectorRef) { }

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

在您的规范文件中,调用您的测试床而不提供ChangeDetectorRef,因为它不会提供您提供的内容.在每个块之前设置相同的组件,以便在规格之间重置,就像在docs here中所做的那样:

component = fixture.componentInstance;

然后在测试中,直接监视属性

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

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

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

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

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

Typescript相关问答推荐

将对象属性路径描述为字符串数组的类型

Typescript如何从字符串中提取多个文本

Angular -使用Phone Directive将值粘贴到控件以格式化值时验证器不工作

在TypeScript中,除了映射类型之外还使用的`in`二进制运算符?

泛型类型,引用其具有不同类型的属性之一

为什么ESLint会抱怨函数调用返回的隐式`any`?

从子类集合实例化类,同时保持对Typescript中静态成员的访问

是否有可能避免此泛型函数体中的类型断言?

缩小对象具有某一类型的任意字段的范围

Angular 16将独立组件作为对话框加载,而不进行布线或预加载

创建一个TypeScrip对象并基于来自输入对象的约束定义其类型

如何扩展RxJS?

typescript如何从映射类型推断

T的typeof键的Typescript定义

将带有额外id属性的Rust struct 展平回TypeScript单个对象,以返回wasm-bindgen函数

两个子组件共享控制权

为什么我无法在 TypeScript 中重新分配函数?

有没有办法从不同长度的元组的联合中提取带有类型的最后一个元素?

TS 条件类型无法识别条件参数

通用可选函数参数返回类型