28

我刚刚开始进行单元测试,我已经能够模拟我自己的服务以及一些 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();
 }
}

我已经尝试了一切async,,,,fakeAsyncinjector([ChangeDetectorRef], () => {})

没有任何效果。

4

5 回答 5

30

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 的情况下调用您的 TestBed,因为它不会提供您提供的内容。将组件设置为相同的 beforeEach 块,因此它在规格之间重置,就像在此处的文档中所做的那样:

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 中不存在,所以它是可访问的。

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

于 2017-05-30T18:45:14.803 回答
7

不确定这是否是新事物,但可以通过夹具访问 changeDetectorRef。

请参阅文档:https ://angular.io/guide/testing#componentfixture-properties

我们在更改检测器模拟中遇到了同样的问题,这最终成为了解决方案

于 2018-06-21T12:39:45.210 回答
2

可能需要指出的一点是,本质上您想测试自己的代码,而不是对变更检测器本身进行单元测试(由 Angular 团队测试)。在我看来,这是一个很好的指标,表明您应该将对更改检测器的调用提取到本地私有方法(私有,因为它是您不想进行单元测试的东西),例如

private detectChanges(): void {
    this.cdRef.detectChanges();
}

然后,在您的单元测试中,您将需要验证您的代码是否实际调用了此函数,从而调用了 ChangeDetectorRef 中的方法。例如:

it('should call the change detector',
    () => {
        const spyCDR = spyOn((cmp as any).cdRef, 'detectChanges' as any);
        cmp.ngOnInit();
        expect(spyCDR).toHaveBeenCalled();
    }
);

我遇到了完全相同的情况,这是一位高级开发人员向我建议的单元测试的一般最佳实践,他告诉我单元测试实际上是在强迫你通过这种模式更好地构建你的代码。通过建议的重组,您可以确保您的代码可以灵活更改,例如,如果 Angular 改变了它们为我们提供更改检测的方式,那么您只需调整 detectChanges 方法。

于 2018-03-13T13:40:10.100 回答
1

对于单元测试,如果您ChangeDetectorRef只是为了满足要创建的组件的依赖注入而进行模拟,则可以传入任何值。

就我而言,我这样做了:

TestBed.configureTestingModule({
  providers: [
    FormBuilder,
    MyComponent,
    { provide: ChangeDetectorRef, useValue: {} }
  ]
}).compileComponents()
injector = getTestBed()
myComponent = injector.get(MyComponent)

它将myComponent成功创建。只要确保测试执行路径不需要ChangeDetectorRef. 如果你这样做了,那么useValue: {}用一个适当的模拟对象替换。

就我而言,我只需要使用FormBuilder.

于 2018-06-20T11:01:32.623 回答
0
// component
constructor(private changeDetectorRef: ChangeDetectorRef) {}

public someHandler() {
  this.changeDetectorRef.detectChanges();
}     

// spec
const changeDetectorRef = fixture.componentRef.changeDetectorRef;
jest.spyOn(changeDetectorRef, 'detectChanges');
fixture.detectChanges(); // <--- needed!!

component.someHandler();

expect(changeDetectorRef.detectChanges).toHaveBeenCalled();
于 2022-01-31T13:57:28.170 回答