Angular 2: How to mock ChangeDetectorRef in unit testing

I just started with Unit-Testing and I was able to mock my services and some of Angular and Ionic, but no matter what I do ChangeDetectorRef remains the same.

I mean, what kind of witchcraft 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 tried everything, async , fakeAsync , injector([ChangeDetectorRef], () => {}) .

Nothing works.

+5
source share
2 answers

In case someone comes across this, this is one of the ways that worked for me well:

How do you insert an instance of ChangeDetectorRef into your constructor:

  constructor(private cdRef: ChangeDetectorRef) { } 

You have cdRef as one of the component's private attributes, which means that you can monitor the component, mute that attribute and return it whatever you want. In addition, you can approve your calls and parameters as needed.

In your spec file, call your TestBed without providing ChangeDetectorRef, since it will not provide what you give it. Set the component to the same before each block, so reset between specifications, as is done in docs here :

 component = fixture.componentInstance; 

Then in the tests the spy is directly by attribute

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

With a spy, you can use .and.returnValue() and return it what you need.

Note that (component as any) used as cdRef , a private attribute. But private does not exist in the most compiled javascript, therefore it is available.

It is up to you if you want to access private attributes at runtime this way for your tests. I personally have no problems with this, I do it according to my specifications in order to get more information.

+7
source

You probably need to point out one point, which is that basically you want to test your own code here, and not the unit test of the change detector itself (which was tested by the Angular team). In my opinion, this is a good indicator that you should output the change detector call to the local private method (private, since this is what you don't want to use unit test for example).

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

Then, in your unit test, you will need to make sure that your code has actually called this function and thus called the method from ChangeDetectorRef. For instance:

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

I had the same situation and it was suggested to me as a general best practice for unit testing from a senior developer who told me that unit testing actually makes you better structure your code according to this template. With the proposed restructuring, you will ensure that your code is flexible for change, for example. if Angular changes the way they provide us with change detection, then you only need to adapt the detectChanges method.

0
source

Source: https://habr.com/ru/post/1262158/


All Articles