Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Angular 2: How to mock ChangeDetectorRef while unit testing

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.

like image 420
Ankit Singh Avatar asked Jan 02 '17 05:01

Ankit Singh


People also ask

What is mocking in unit testing Angular?

Introduction. Mocking is a great idea for testing Angular apps because it makes maintenance easier and helps reduce future bugs. There are a few complex tools, such as XUnit, for mocking an Angular CLI project. You can execute the mocking methods described in this guide only if you use vanilla Jasmine + Angular Testbed ...

What does ChangeDetectorRef detectChanges () do?

detectChanges()link Checks this view and its children. Use in combination with detach to implement local change detection checks.

What is ChangeDetectorRef Angular?

ChangeDetectorRef allows you to manipulate this tree, angular runs the change detection every time there is a change. For example, when you have a large amount of data and there is a change in that data, angular will render the view again, if the data changes every second, your application will become slower.

Which method is used to mock the service methods response in Angular?

The TestBed. configureTestingModule() method takes a metadata object that can have most of the properties of an @NgModule. To test a service, you set the providers metadata property with an array of the services that you'll test or mock. content_copy let service: ValueService; beforeEach(() => { TestBed.


1 Answers

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.

like image 106
Juan Avatar answered Oct 14 '22 08:10

Juan