typescript Angular 2:如何在单元测试时模拟ChangeDetectorRef

cx6n0qe3  于 2023-05-01  发布在  TypeScript
关注(0)|答案(6)|浏览(142)

我刚刚开始使用单元测试,我已经能够模拟我自己的服务,以及一些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], () => {})
什么都不管用。

iyfjxgzm

iyfjxgzm1#

更新2020

我最初在2017年5月写了这篇文章,这是一个当时效果很好的解决方案,现在仍然有效。
我们不能通过测试床配置changeDetectorRef mock的注入,所以这是我这些天正在做的事情:

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作为组件上的私有属性之一,这意味着您可以监视组件,存根该属性并让它返回任何您想要的内容。此外,您还可以根据需要Assert其调用和参数。
在你的spec文件中,调用你的TestBed而不提供ChangeDetectorRef,因为它不会提供你给予它的东西。设置相同的组件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是私有属性。但是private并不存在于实际编译的javascript中,所以它是可访问的。
如果您希望在运行时以这种方式访问私有属性,这取决于您。

1yjd4xko

1yjd4xko2#

不确定这是否是一个新的东西,但changeDetectorRef可以通过fixture访问。
参见文档: www.example.com
我们遇到了相同的问题与变化检测器模拟,这是结束了解决方案

v9tzhpje

v9tzhpje3#

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

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方法。

vc9ivgsu

vc9ivgsu4#

对于单元测试,如果你模拟ChangeDetectorRef只是为了满足要创建的组件的依赖注入,你可以传入任何值。
对于我的情况,我是这样做的:

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

它将成功创建myComponent。只需确保测试执行路径不需要ChangeDetectorRef即可。如果是这样,那么用一个适当的mock对象替换useValue: {}
在我的例子中,我只需要使用FormBuilder测试一些表单创建的东西。

arknldoa

arknldoa5#

// 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();
iswrvxsc

iswrvxsc6#

我看到了很多好的答案。
2023年,我开玩笑地说:

it('detects changes', () => {
  const changeDetectorRef = fixture.changeDetectorRef; 
   
  // Spying your method.
  jest.spyOn(changeDetectorRef, 'detectChanges');

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

  expect(changeDetectorRef.detectChanges).toHaveBeenCalled();
});

相关问题